-
Notifications
You must be signed in to change notification settings - Fork 5
Contacts API #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Contacts API #49
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
require 'mailtrap' | ||
|
||
client = Mailtrap::Client.new(api_key: 'your-api-key') | ||
contact_list = Mailtrap::ContactListsAPI.new 3229, client | ||
contacts = Mailtrap::ContactsAPI.new 3229, client | ||
|
||
# Set your API credentials as environment variables | ||
# export MAILTRAP_API_KEY='your-api-key' | ||
# export MAILTRAP_ACCOUNT_ID=your-account-id | ||
# | ||
# contact_list = Mailtrap::ContactListsAPI.new | ||
# contacts = Mailtrap::ContactsAPI.new | ||
|
||
# Create new contact list | ||
list = contact_list.create(name: 'Test List') | ||
|
||
# Get all contact lists | ||
contact_list.list | ||
|
||
# Update contact list | ||
contact_list.update(list.id, name: 'Test List Updated') | ||
|
||
# Get contact list | ||
list = contact_list.get(list.id) | ||
|
||
# Create new contact | ||
contact = contacts.create(email: '[email protected]', fields: { first_name: 'John Doe' }, list_ids: [list.id]) | ||
|
||
# Get contact | ||
contact = contacts.get(contact.id) | ||
|
||
# Update contact using id | ||
updated_contact = contacts.update(contact.id, email: '[email protected]', fields: { first_name: 'Jane Doe' }, | ||
list_ids_excluded: [list.id]) | ||
|
||
# Update contact using email | ||
contacts.update(updated_contact.data.email, email: '[email protected]', fields: { first_name: 'Jane Doe' }, | ||
list_ids_included: [list.id]) | ||
|
||
# Delete contact | ||
contacts.delete(contact.id) | ||
|
||
# Delete contact list | ||
contact_list.delete(list.id) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# frozen_string_literal: true | ||
|
||
module Mailtrap | ||
class BaseAPI | ||
attr_reader :account_id, :client | ||
|
||
# @param account_id [Integer] The account ID | ||
# @param client [Mailtrap::Client] The client instance | ||
# @raise [ArgumentError] If account_id is nil | ||
def initialize(account_id = ENV.fetch('MAILTRAP_ACCOUNT_ID'), client = Mailtrap::Client.new) | ||
raise ArgumentError, 'account_id is required' if account_id.nil? | ||
|
||
@account_id = account_id | ||
@client = client | ||
end | ||
|
||
private | ||
|
||
def validate_options!(options, supported_options) | ||
invalid_options = options.keys - supported_options | ||
return if invalid_options.empty? | ||
|
||
raise ArgumentError, "invalid options are given: #{invalid_options}, supported_options: #{supported_options}" | ||
end | ||
|
||
def build_entity(options, response_class) | ||
response_class.new(options.slice(*response_class.members)) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# frozen_string_literal: true | ||
|
||
module Mailtrap | ||
# Data Transfer Object for Contact | ||
# @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/220a54e31e5ca-contact | ||
# @attr_reader id [String] The contact ID | ||
# @attr_reader email [String] The contact's email address | ||
# @attr_reader fields [Hash] Object of fields with merge tags | ||
# @attr_reader list_ids [Array<Integer>] Array of list IDs | ||
# @attr_reader status [String] The contact status (subscribed/unsubscribed) | ||
# @attr_reader created_at [Integer] The creation timestamp | ||
# @attr_reader updated_at [Integer] The last update timestamp | ||
Contact = Struct.new( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @IgorDobryn if the only motivation for resource classes it to achieve strictness, then we can do this: contact_hash = {id: 1}
contact_hash.default_proc = proc { |_, k| raise "unknown key '#{k}'" } Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can work, but rather unexpected. So, I'd prefer to keep separate class |
||
:id, | ||
:email, | ||
:fields, | ||
:list_ids, | ||
:status, | ||
:created_at, | ||
:updated_at, | ||
keyword_init: true | ||
) do | ||
# @return [Hash] The contact attributes as a hash | ||
def to_h | ||
super.compact | ||
end | ||
end | ||
|
||
# Data Transfer Object for Contact Update Response | ||
# @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/16eab4fff9740-contact-update-response | ||
# @attr_reader action [String] The performed action (created/updated) | ||
# @attr_reader data [Contact, Hash] The contact data | ||
ContactUpdateResponse = Struct.new(:action, :data, keyword_init: true) do | ||
def initialize(*) | ||
super | ||
self.data = Contact.new(data) if data.is_a?(Hash) | ||
end | ||
|
||
# @return [Hash] The response attributes as a hash | ||
def to_h | ||
super.compact | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# frozen_string_literal: true | ||
|
||
module Mailtrap | ||
# Data Transfer Object for Contact List | ||
# @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/6ec7a37234af2-contact-list | ||
# @attr_reader id [Integer] The contact list ID | ||
# @attr_reader name [String] The name of the contact list | ||
ContactList = Struct.new(:id, :name, keyword_init: true) do | ||
# @return [Hash] The contact list attributes as a hash | ||
def to_h | ||
super.compact | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative 'contact_list' | ||
|
||
module Mailtrap | ||
class ContactListsAPI < BaseAPI | ||
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you plan to add Contact Fields API support? |
||
SUPPORTED_OPTIONS = %i[name].freeze | ||
private_constant :SUPPORTED_OPTIONS | ||
|
||
# Retrieves a specific contact list | ||
# @param list_id [Integer] The contact list identifier | ||
# @return [ContactList] Contact list object | ||
# @!macro api_errors | ||
def get(list_id) | ||
response = client.get("#{base_path}/#{list_id}") | ||
build_entity(response, ContactList) | ||
end | ||
|
||
# Creates a new contact list | ||
# @param [Hash] options The parameters to create | ||
# @option options [String] :name The contact list name | ||
# @return [ContactList] Created contact list object | ||
# @!macro api_errors | ||
# @raise [ArgumentError] If invalid options are provided | ||
def create(options) | ||
validate_options!(options, SUPPORTED_OPTIONS) | ||
|
||
response = client.post(base_path, options) | ||
build_entity(response, ContactList) | ||
end | ||
|
||
# Updates an existing contact list | ||
# @param list_id [Integer] The contact list ID | ||
# @param [Hash] options The parameters to update | ||
# @option options [String] :name The contact list name | ||
# @return [ContactList] Updated contact list object | ||
# @!macro api_errors | ||
# @raise [ArgumentError] If invalid options are provided | ||
def update(list_id, options) | ||
validate_options!(options, SUPPORTED_OPTIONS) | ||
|
||
response = client.patch( | ||
"#{base_path}/#{list_id}", options | ||
) | ||
build_entity(response, ContactList) | ||
end | ||
|
||
# Deletes a contact list | ||
# @param list_id [Integer] The contact list ID | ||
# @return nil | ||
# @!macro api_errors | ||
def delete(list_id) | ||
client.delete("#{base_path}/#{list_id}") | ||
end | ||
|
||
# Lists all contact lists for the account | ||
# @return [Array<ContactList>] Array of contact list objects | ||
# @!macro api_errors | ||
def list | ||
response = client.get(base_path) | ||
response.map { |list| build_entity(list, ContactList) } | ||
end | ||
|
||
private | ||
|
||
def base_path | ||
"/api/accounts/#{account_id}/contacts/lists" | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative 'contact' | ||
require 'ostruct' | ||
|
||
module Mailtrap | ||
class ContactsAPI < BaseAPI | ||
CREATE_SUPPORTED_OPTIONS = %i[email fields list_ids].freeze | ||
UPDATE_SUPPORTED_OPTIONS = %i[email fields list_ids_included list_ids_excluded unsubscribed].freeze | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe
|
||
private_constant :CREATE_SUPPORTED_OPTIONS, :UPDATE_SUPPORTED_OPTIONS | ||
|
||
# Retrieves a specific contact | ||
# @param contact_id [String] The contact identifier, which can be either a UUID or an email address | ||
# @return [Contact] Contact object | ||
# @!macro api_errors | ||
def get(contact_id) | ||
response = client.get("#{base_path}/#{contact_id}") | ||
build_entity(response[:data], Contact) | ||
end | ||
|
||
# Creates a new contact | ||
# @param [Hash] options The parameters to create | ||
# @option options [String] :email The contact's email address | ||
# @option options [Hash] :fields The contact's fields | ||
# @option options [Array<Integer>] :list_ids The contact's list IDs | ||
# @return [Contact] Created contact object | ||
# @!macro api_errors | ||
# @raise [ArgumentError] If invalid options are provided | ||
def create(options) | ||
validate_options!(options, CREATE_SUPPORTED_OPTIONS) | ||
|
||
response = client.post(base_path, { contact: options }) | ||
build_entity(response[:data], Contact) | ||
end | ||
|
||
# Updates an existing contact | ||
# @param contact_id [String] The contact ID or email address | ||
# @param [Hash] options The parameters to update | ||
# @option options [String] :email The contact's email address | ||
# @option options [Hash] :fields The contact's fields | ||
# @option options [Array<Integer>] :list_ids_included The contact's list IDs to include | ||
# @option options [Array<Integer>] :list_ids_excluded The contact's list IDs to exclude | ||
# @option options [Boolean] :unsubscribed Whether to unsubscribe the contact | ||
# @return [ContactUpdateResponse] Updated contact object | ||
# @!macro api_errors | ||
# @raise [ArgumentError] If invalid options are provided | ||
def update(contact_id, options) | ||
validate_options!(options, UPDATE_SUPPORTED_OPTIONS) | ||
|
||
response = client.patch( | ||
"#{base_path}/#{contact_id}", | ||
{ contact: options } | ||
) | ||
build_entity(response, ContactUpdateResponse) | ||
end | ||
|
||
# Deletes a contact | ||
# @param contact_id [String] The contact ID | ||
# @return nil | ||
# @!macro api_errors | ||
def delete(contact_id) | ||
client.delete("#{base_path}/#{contact_id}") | ||
end | ||
|
||
private | ||
|
||
def base_path | ||
"/api/accounts/#{account_id}/contacts" | ||
end | ||
end | ||
end |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class has a bunch of helper methods. I wonder if it should be a mixin instead.
As an alternative, you could add methods to the base class, making inheritance more meaningful:
See also the abstract yard annotation tag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't go with mixin, cause main goal was to share
#initialize
method, and having two entities(base class and mixin) for 3 method seem over-engineered to me.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also I can advice to have base implementations for all methods here
get
,create
, etc..and to make that possible I would introduce
and it will be implemented like
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that what I meant. Though keep in mind that ruby is not java, thus you don't need to implement methods with
raise NotImplementedError
. Use abstract annotation instead.Through there is one problem. Not all APIs support all CRUD methods. For example, there is no
list
in the Contacts API. In this case overriding withraise
might be relevant. If you choose to go this route please mindrespond_to?
.As a side note, you do can put
initialize
in a mixin.UPD
NotImplementedError
is commonly misused. But this is so common that you can say its the standard.