cakemail-next-gen 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.env.dist +3 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +52 -0
  5. data/LICENSE +21 -0
  6. data/README.md +194 -0
  7. data/Rakefile +12 -0
  8. data/cakemail.gemspec +29 -0
  9. data/images/logo.png +0 -0
  10. data/lib/cakemail/base.rb +248 -0
  11. data/lib/cakemail/configuration.rb +21 -0
  12. data/lib/cakemail/connection.rb +52 -0
  13. data/lib/cakemail/contact.rb +57 -0
  14. data/lib/cakemail/list.rb +35 -0
  15. data/lib/cakemail/sender.rb +30 -0
  16. data/lib/cakemail/token.rb +59 -0
  17. data/lib/cakemail/version.rb +5 -0
  18. data/lib/cakemail.rb +21 -0
  19. data/sig/cakemail.rbs +4 -0
  20. data/vcr_cassettes/contact_create.yml +45 -0
  21. data/vcr_cassettes/contact_create_from_list.yml +45 -0
  22. data/vcr_cassettes/contact_unsubscribe.yml +45 -0
  23. data/vcr_cassettes/contact_update.yml +45 -0
  24. data/vcr_cassettes/contacts_all_without_error.yml +45 -0
  25. data/vcr_cassettes/contacts_delete.yml +45 -0
  26. data/vcr_cassettes/contacts_for_deletation.yml +45 -0
  27. data/vcr_cassettes/contacts_for_unsubscribe.yml +45 -0
  28. data/vcr_cassettes/contacts_for_update.yml +45 -0
  29. data/vcr_cassettes/contacts_lists.yml +49 -0
  30. data/vcr_cassettes/create_token.yml +45 -0
  31. data/vcr_cassettes/create_wrong_token.yml +46 -0
  32. data/vcr_cassettes/list.yml +46 -0
  33. data/vcr_cassettes/list_archive.yml +45 -0
  34. data/vcr_cassettes/list_contacts.yml +45 -0
  35. data/vcr_cassettes/list_create.yml +46 -0
  36. data/vcr_cassettes/list_delete.yml +45 -0
  37. data/vcr_cassettes/list_sender.yml +46 -0
  38. data/vcr_cassettes/list_unarchive.yml +45 -0
  39. data/vcr_cassettes/list_update.yml +46 -0
  40. data/vcr_cassettes/lists.yml +49 -0
  41. data/vcr_cassettes/lists_count.yml +47 -0
  42. data/vcr_cassettes/lists_find_in_batches.yml +93 -0
  43. data/vcr_cassettes/lists_for_archive.yml +50 -0
  44. data/vcr_cassettes/lists_for_deletation.yml +50 -0
  45. data/vcr_cassettes/lists_for_unarchive.yml +48 -0
  46. data/vcr_cassettes/lists_for_update.yml +50 -0
  47. data/vcr_cassettes/senders.yml +46 -0
  48. metadata +93 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 95edcd5ef79bdd5c936ed0e1796dea3e69a2c0b9c91b7e8c22d4a96a35bdc022
4
+ data.tar.gz: 6c9771c0061bd3268833293db53f3b5caecadbf09389303333fa4056674cef11
5
+ SHA512:
6
+ metadata.gz: b7cfcf78af7aed0e39e83d6a507035631046aeeb7eec499b4cf23bbcadcf336ad76083bb5d64c73f65c6a98208a8667c83c421685c6b7785cdb5950a6d3a85cd
7
+ data.tar.gz: 06db8e184b081f991a2f35bc0ed76affd657a03b91a1545080d627b4a9f576ed261bdf58e02e13c13129e23c9443cbe2667cb681cd91972ba278e18767d7fc6a
data/.env.dist ADDED
@@ -0,0 +1,3 @@
1
+ CAKEMAIL_API_KEY="..."
2
+ CAKEMAIL_USERNAME="..."
3
+ CAKEMAIL_PASSWORD="..."
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,52 @@
1
+ Layout/FirstHashElementIndentation:
2
+ Enabled: false
3
+ Layout/ArgumentAlignment:
4
+ Enabled: false
5
+ Layout/HashAlignment:
6
+ Enabled: false
7
+ Style/Documentation:
8
+ Enabled: false
9
+ Style/FrozenStringLiteralComment:
10
+ Enabled: false
11
+ Style/OptionalBooleanParameter:
12
+ Enabled: false
13
+ Style/MethodDefParentheses:
14
+ Enabled: true
15
+ Style/StringLiterals:
16
+ EnforcedStyle: double_quotes
17
+ Style/RescueStandardError:
18
+ EnforcedStyle: implicit
19
+ Naming/VariableNumber:
20
+ CheckSymbols: false
21
+ Style/VariableNumber:
22
+ Enabled: false
23
+ Style/HashSyntax:
24
+ EnforcedShorthandSyntax: never
25
+ Gemspec/RequiredRubyVersion:
26
+ Enabled: false
27
+ Style/WordArray:
28
+ Enabled: false
29
+ Style/SymbolArray:
30
+ Enabled: false
31
+ Lint/Debugger:
32
+ Enabled: false
33
+ Layout/MultilineMethodCallIndentation:
34
+ Enabled: false
35
+ Style/TrailingCommaInArguments:
36
+ EnforcedStyleForMultiline: comma
37
+ Layout/ArrayAlignment:
38
+ EnforcedStyle: with_fixed_indentation
39
+ Style/ClassVars:
40
+ Enabled: false
41
+ Metrics/BlockLength:
42
+ Max: 2000
43
+ Metrics/MethodLength:
44
+ Max: 25
45
+ Metrics/PerceivedComplexity:
46
+ Max: 15
47
+ Metrics/AbcSize:
48
+ Max: 25
49
+ Metrics/CyclomaticComplexity:
50
+ Max: 20
51
+ Metrics/ClassLength:
52
+ Max: 250
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Nathan Lopez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,194 @@
1
+ <p align="center">
2
+ <img src="images/logo.png" alt="Cakemail Next-gen" />
3
+ </p>
4
+
5
+ # Cakemail Next-gen Ruby wrapper
6
+
7
+ <span>[![Gem Version](https://img.shields.io/gem/v/cakemail-next-gen.svg?label=cakemail-next-gen&colorA=D30001&colorB=DF3B3C)](https://rubygems.org/gems/cakemail-next-gen)</span> <span>
8
+ [![ruby](https://img.shields.io/badge/ruby-2.6+-ruby.svg?colorA=D30001&colorB=DF3B3C)](https://github.com/andrewdsilva/cakemail-ruby)</span> <span>
9
+ ![Rubocop Status](https://img.shields.io/badge/rubocop-passing-rubocop.svg?colorA=1f7a1f&colorB=2aa22a)</span> <span>
10
+ [![MIT license](https://img.shields.io/badge/license-MIT-mit.svg?colorA=1f7a1f&colorB=2aa22a)](http://opensource.org/licenses/MIT)</span> <span>
11
+ ![Downloads](https://img.shields.io/gem/dt/cakemail-next-gen.svg?colorA=004d99&colorB=0073e6)</span>
12
+
13
+ This library allows you to quickly and easily use the Cakemail Next-gen API via Ruby.
14
+
15
+ This gem is designed to be community-driven, with the aim of prioritizing the needs and preferences of its users. Your active involvement is crucial in shaping the future development of this project. You can contribute by creating issues and pull requests, as well as engaging with existing ones through upvoting or commenting.
16
+
17
+ If you need help using Cakemail, please check the Cakemail Support Help Center at https://www.cakemail.com/contact-us.
18
+
19
+ ## Installation
20
+
21
+ To install the gem add it into a Gemfile (Bundler):
22
+
23
+ ```ruby
24
+ gem "cakemail-next-gen"
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ ```
30
+ bundle install
31
+ ```
32
+
33
+ ## API key
34
+
35
+ You need to set your Cakemail API credentials using environment variables.
36
+
37
+ ```ruby
38
+ # .env
39
+ CAKEMAIL_API_KEY="..."
40
+ CAKEMAIL_USERNAME="..."
41
+ CAKEMAIL_PASSWORD="..."
42
+ ```
43
+
44
+ If you wish to generate a new token, you will need the environment variables `CAKEMAIL_USERNAME` and `CAKEMAIL_PASSWORD`.
45
+
46
+ If you already have a token, you will only need: `CAKEMAIL_API_KEY`.
47
+
48
+ ### Generating a Token
49
+
50
+ To generate a token, follow these steps:
51
+
52
+ 1. Set the `CAKEMAIL_USERNAME` and `CAKEMAIL_PASSWORD` environment variables using a `.env` file.
53
+ 2. Use the `Cakemail::Token.create` method provided by the gem to generate a token. Here's an example of how to use it :
54
+
55
+ ```ruby
56
+ token = Cakemail::Token.create
57
+
58
+ pp "Access token created is #{token.access_token}"
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ Cakemail-Ruby was designed with usability as its primary goal, so that you can forget the API and intuitively interact with your lists, contacts, campaigns and so on.
64
+
65
+ ## Lists
66
+
67
+ Here are some basic usage examples for managing lists:
68
+
69
+ 1. Fetching all lists:
70
+ ```ruby
71
+ lists = Cakemail::List.all
72
+ lists.each do |list|
73
+ puts "List ID: #{list.id}"
74
+ puts "List Name: #{list.name}"
75
+ # Additional list attributes can be accessed here
76
+ end
77
+ ```
78
+
79
+ 2. Creating a new list:
80
+ ```ruby
81
+ new_list = Cakemail::List.create(name: "New List")
82
+ puts "Created List ID: #{new_list.id}"
83
+ puts "Created List Name: #{new_list.name}"
84
+ ```
85
+
86
+ 3. Updating an existing list:
87
+ ```ruby
88
+ list = Cakemail::List.find(list_id)
89
+ list.update(name: "New name")
90
+ puts "Updated List Name: #{list.name}"
91
+ ```
92
+
93
+ 4. Deleting a list:
94
+ ```ruby
95
+ list = Cakemail::List.find(list_id)
96
+ list.delete
97
+ puts "List deleted successfully."
98
+ ```
99
+
100
+ 5. Archiving or unarchiving a list:
101
+ ```ruby
102
+ list = Cakemail::List.find(list_id)
103
+ list.archive
104
+ # List is archived
105
+ list.unarchive
106
+ # List is unarchived
107
+ ```
108
+
109
+ ## Handling large data sets
110
+
111
+ The `find_in_batches` method, similar to its counterpart in ActiveRecord, allows you to retrieve records from a collection in batches. This is useful when dealing with large datasets, as it helps overcome API limitations by retrieving data in batches of 50, rather than loading the entire collection into memory at once.
112
+
113
+ Here's an example of how to use `find_in_batches` in the Cakemail Ruby Gem:
114
+
115
+ ```ruby
116
+ Cakemail::List.find_in_batches do |list|
117
+ puts list
118
+ end
119
+ ```
120
+
121
+ ## Contacts
122
+
123
+ Here are some common use cases for managing contacts:
124
+
125
+ 1. Retrieve all contacts in a list
126
+
127
+ To retrieve all contacts in a given list, you can use the `list` method of the `Cakemail::Contact` class. Make sure to specify the parent list when calling the method:
128
+
129
+ ```ruby
130
+ # Get lists
131
+ lists = Cakemail::List.all
132
+
133
+ # Retrieve all contacts in a list
134
+ contacts = Cakemail::Contact.list(parent: lists.first)
135
+
136
+ contacts.each do |contact|
137
+ puts "Email: #{contact.email}"
138
+ puts "Status: #{contact.status}"
139
+ puts "Subscription Date: #{Time.at(contact.subscribed_on)}"
140
+ puts "---"
141
+ end
142
+
143
+ # Or directly from a list object
144
+ lists.first.contacts
145
+ ```
146
+
147
+ 2. Create a new contact
148
+
149
+ To create a new contact in a specific list, use the `create` method of the `Cakemail::Contact` class. Provide the necessary information, such as the email address, as a parameter hash:
150
+
151
+ ```ruby
152
+ # Create a new contact
153
+ params = {
154
+ email: "nathan.lopez042@gmail.com"
155
+ }
156
+
157
+ new_contact = Cakemail::Contact.create(params, parent: list)
158
+
159
+ puts "Contact created successfully."
160
+ puts "Email: #{new_contact.email}"
161
+ puts "Status: #{new_contact.status}"
162
+
163
+ # Or create from list
164
+
165
+ new_contact = list.create_contact(params)
166
+ puts "Contact created successfully."
167
+ ```
168
+
169
+ 3. Updating an existing contact:
170
+ ```ruby
171
+ list = Cakemail::List.find(list_id)
172
+ contact = list.contacts.last
173
+
174
+ contact.update(email: "new_mail@gmail.com")
175
+ puts "Updated contact email: #{contact.email}"
176
+ ```
177
+
178
+ 4. Deleting a contact:
179
+ ```ruby
180
+ list = Cakemail::List.find(list_id)
181
+ contact = list.contacts.last
182
+
183
+ contact.delete
184
+ puts "Contact deleted successfully."
185
+ ```
186
+
187
+ 5. Unsubscribe a contact:
188
+ ```ruby
189
+ list = Cakemail::List.find(list_id)
190
+ contact = list.contacts.last
191
+
192
+ contact.unsubscribe
193
+ puts "Contact unsubscribed successfully."
194
+ ```
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/cakemail.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/cakemail/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "cakemail-next-gen"
7
+ spec.version = Cakemail::VERSION
8
+ spec.authors = ["Nathan Lopez"]
9
+ spec.email = ["nathan.lopez042@gmail.com"]
10
+
11
+ spec.summary = "This library allows you to quickly and easily use the Cakemail Next-gen API via Ruby."
12
+ spec.description = "This library allows you to quickly and easily use the Cakemail Next-gen API via Ruby."
13
+ spec.homepage = "https://github.com/andrewdsilva/cakemail-ruby"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/andrewdsilva/cakemail-ruby"
19
+
20
+ spec.files = Dir.chdir(__dir__) do
21
+ `git ls-files -z`.split("\x0").reject do |f|
22
+ (File.expand_path(f) == __FILE__) ||
23
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
24
+ end
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+ end
data/images/logo.png ADDED
Binary file
@@ -0,0 +1,248 @@
1
+ module Cakemail
2
+ # @attr [Integer] id Id of the list
3
+ class Base
4
+ class NoParentError < StandardError; end
5
+
6
+ attr_accessor :id, :parent
7
+
8
+ # Returns Cakemail object with id and type provided
9
+ #
10
+ # @param id [String]
11
+ # @return [List, Contact, Campaign, Tags]
12
+ #
13
+ # @example
14
+ # list = Cakemail::List.find(1)
15
+ # list.class #=> Cakemail::User
16
+ # list.id #=> 1
17
+ def self.find(id, options = {})
18
+ parent = get_parent(options)
19
+
20
+ path = "#{object_class.path}/#{id}"
21
+ path = path_with_parent(path, parent) if parent
22
+
23
+ response = Cakemail.get path
24
+
25
+ instantiate_object response["data"] unless response.nil?
26
+ end
27
+
28
+ # Returns Cakemail objects
29
+ #
30
+ # @param page [Integer]
31
+ # @param per_page [Integer]
32
+ # @return [Array<List>, Array<Contact>, Array<Campaign>, Array<Tags>]
33
+ #
34
+ # @example
35
+ # contacts = Cakemail::Contact.list(1, 50)
36
+ def self.list(options = {})
37
+ page = options[:page] || 1
38
+ per_page = options[:per_page] || 50
39
+ filters = options[:filters] || {}
40
+ parent = options[:parent] || nil
41
+
42
+ no_parent_exception if parent_required && parent.nil?
43
+
44
+ path = "#{object_class.path}?page=#{page}&per_page=#{per_page}"
45
+ path = path_with_parent(path, parent) if parent
46
+
47
+ unless filters.keys.empty?
48
+ query = filters.map { |key, value| "filter=#{key}==#{value}" }.join("&")
49
+
50
+ path += "&#{query}"
51
+ end
52
+
53
+ response = Cakemail.get path
54
+
55
+ instantiate_object_list(response["data"], parent) unless response.nil?
56
+ end
57
+
58
+ # Returns total count
59
+ #
60
+ # @return Integer
61
+ #
62
+ # @example
63
+ # total_contacts = Cakemail::Contact.count
64
+ def self.count(options = {})
65
+ parent = get_parent(options)
66
+
67
+ path = "#{object_class.path}?page=1&per_page=1&with_count=true"
68
+ path = path_with_parent(path, parent) if parent
69
+
70
+ response = Cakemail.get path
71
+
72
+ response["pagination"]["count"] unless response.nil?
73
+ end
74
+
75
+ # Yields each batch of records that was found
76
+ #
77
+ # @param block [Block]
78
+ #
79
+ # @example
80
+ # total_contacts = Cakemail::Contact.count
81
+ def self.find_in_batches(options = {}, &block)
82
+ total = count(options)
83
+
84
+ per_page = options[:per_page] || 50
85
+ page = 1
86
+
87
+ loop do
88
+ list(options.merge(page: page, per_page: per_page)).each do |object|
89
+ block.call(object)
90
+ end
91
+
92
+ break if page * per_page > total
93
+
94
+ page += 1
95
+ end
96
+ end
97
+
98
+ # Create a new object and return it
99
+ #
100
+ # @param params [Hash]
101
+ # @return [List, Contact, Campaign, Tags]
102
+ #
103
+ # @example
104
+ # my_list = Cakemail::List.create(name: "My list")
105
+ def self.create(params, options = {})
106
+ parent = get_parent(options)
107
+
108
+ no_parent_exception if parent_required && parent.nil?
109
+
110
+ path = object_class.path
111
+ path = path_with_parent(path, parent) if parent
112
+
113
+ response = Cakemail.post path, params.to_json
114
+
115
+ return response unless response_ok? response
116
+
117
+ instantiate_object(response["data"]) unless response.nil?
118
+ end
119
+
120
+ # Delete Cakemail object
121
+ #
122
+ # @return Boolean
123
+ #
124
+ # @example
125
+ # list = Cakemail::List.find(1)
126
+ # list.delete
127
+ def delete(options = {})
128
+ parent = get_parent(options)
129
+
130
+ path = "#{self.class.object_class.path}/#{id}"
131
+ path = self.class.path_with_parent(path, parent) if parent
132
+
133
+ response = Cakemail.delete path
134
+
135
+ self.class.response_ok?(response) && response["deleted"]
136
+ end
137
+
138
+ # Update Cakemail object and return it
139
+ #
140
+ # @return [List, Contact, Campaign, Tags]
141
+ #
142
+ # @example
143
+ # list = Cakemail::List.find(1)
144
+ # list.update(name: "My list 2")
145
+ def update(params, options = {})
146
+ parent = get_parent(options)
147
+
148
+ path = "#{self.class.object_class.path}/#{id}"
149
+ path = self.class.path_with_parent(path, parent) if parent
150
+
151
+ response = Cakemail.patch path, params.to_json
152
+
153
+ return response unless self.class.response_ok?(response) && response["updated"]
154
+
155
+ self.class.instantiate_object(response["data"]) unless response.nil?
156
+ end
157
+
158
+ # Archive Cakemail object and return it
159
+ #
160
+ # @return [List, Contact, Campaign, Tags]
161
+ #
162
+ # @example
163
+ # list = Cakemail::List.find(1)
164
+ # list.archive
165
+ def archive(options = {})
166
+ parent = get_parent(options)
167
+
168
+ path = "#{self.class.object_class.path}/#{id}/archive"
169
+ path = self.class.path_with_parent(path, parent) if parent
170
+
171
+ response = Cakemail.post path, {}.to_json
172
+
173
+ return response unless self.class.response_ok?(response) && response["archived"]
174
+
175
+ self.class.instantiate_object(response) unless response.nil?
176
+ end
177
+
178
+ # Unarchive Cakemail object and return it
179
+ #
180
+ # @return [List, Contact, Campaign, Tags]
181
+ #
182
+ # @example
183
+ # list = Cakemail::List.find(1)
184
+ # list.unarchive
185
+ def unarchive(options = {})
186
+ parent = get_parent(options)
187
+
188
+ path = "#{self.class.object_class.path}/#{id}/unarchive"
189
+ path = self.class.path_with_parent(path, parent) if parent
190
+
191
+ response = Cakemail.post path, {}.to_json
192
+
193
+ return response unless self.class.response_ok?(response) && !response["archive"]
194
+
195
+ self.class.instantiate_object(response) unless response.nil?
196
+ end
197
+
198
+ def self.get_parent(options = {})
199
+ options[:parent] || nil
200
+ end
201
+
202
+ def get_parent(options = {})
203
+ @parent || self.class.get_parent(options)
204
+ end
205
+
206
+ def self.no_parent_exception
207
+ raise NoParentError
208
+ end
209
+
210
+ def self.parent_required
211
+ false
212
+ end
213
+
214
+ def self.path_with_parent(path, parent)
215
+ "#{parent.class.path}/#{parent.id}/#{path}"
216
+ end
217
+
218
+ def self.response_ok?(response)
219
+ [200, 201].include?(response["status_code"])
220
+ end
221
+
222
+ def initialize(options = {})
223
+ @id = options["id"]
224
+ end
225
+
226
+ def self.object_class; end
227
+
228
+ def self.instantiate_object_list(json, parent = nil)
229
+ json.map { |json_object| instantiate_object(json_object, parent) }
230
+ end
231
+
232
+ def self.instantiate_object(json, parent = nil)
233
+ object = object_class.new json
234
+
235
+ object.parent = parent unless parent.nil?
236
+
237
+ object
238
+ end
239
+
240
+ def respond_to?(method_name)
241
+ attr = "@#{method_name}"
242
+
243
+ return super if method_name.match(/[?!]$/) || !instance_variable_defined?(attr)
244
+
245
+ true
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,21 @@
1
+ require "dotenv/load"
2
+
3
+ module Cakemail
4
+ module Configuration
5
+ def self.api_key
6
+ @api_key
7
+ end
8
+
9
+ def self.api_key=(api_key)
10
+ @api_key = api_key
11
+ end
12
+
13
+ DEFAULT = {
14
+ api_key: ENV["CAKEMAIL_API_KEY"]
15
+ }.freeze
16
+
17
+ DEFAULT.each do |param, default_value|
18
+ send("#{param}=", default_value)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,52 @@
1
+ require "dotenv/load"
2
+ require "faraday"
3
+
4
+ module Cakemail
5
+ class MissingAuthentication < StandardError; end
6
+ class JsonResponseError < StandardError; end
7
+
8
+ API_URI = "https://api.cakemail.dev".freeze
9
+ VERBS = %w[get post patch delete].freeze
10
+
11
+ class << self
12
+ attr_accessor :raw_response
13
+
14
+ VERBS.each do |verb|
15
+ define_method verb do |path, params = {}, headers = {}|
16
+ send_request(verb, path, params, headers)
17
+ end
18
+ end
19
+
20
+ def api_key
21
+ Cakemail.config.api_key
22
+ end
23
+
24
+ private
25
+
26
+ def parse_json(json)
27
+ JSON.parse(json)
28
+ rescue
29
+ raise JsonResponseError
30
+ end
31
+
32
+ def missing_authentication?(response)
33
+ parse_json(response.body)&.dig("detail") == "Not authenticated"
34
+ end
35
+
36
+ def auth_header
37
+ headers = { "accept" => "application/json", "Content-Type" => "application/json" }
38
+ headers = headers.merge("authorization" => "Bearer #{api_key}") if api_key
39
+
40
+ headers
41
+ end
42
+
43
+ def send_request(http_verb, path, params, headers)
44
+ connection = Faraday.new(url: API_URI)
45
+ response = connection.send(http_verb.downcase.to_sym, path, params, auth_header.merge(headers))
46
+
47
+ raise MissingAuthentication if missing_authentication? response
48
+
49
+ parse_json(response.body).merge("status_code" => response.status)
50
+ end
51
+ end
52
+ end