From 7a405c4b3dd923fdedc57c45fe1471b39348f1ce Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 00:37:48 +0200 Subject: upgrade httpoison and poison --- mix.exs | 4 ++-- mix.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index 4b9ffc4..5a0e014 100644 --- a/mix.exs +++ b/mix.exs @@ -23,8 +23,8 @@ defmodule Mailchimp.Mixfile do end defp deps do - [{:httpoison, "~> 0.6"}, - {:poison, "~> 1.4.0"}] + [{:httpoison, "~> 0.7.3"}, + {:poison, "~> 1.5.0"}] end defp package do diff --git a/mix.lock b/mix.lock index 7646230..9922629 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ -%{"hackney": {:hex, :hackney, "1.1.0"}, - "httpoison": {:hex, :httpoison, "0.6.2"}, +%{"hackney": {:hex, :hackney, "1.3.2"}, + "httpoison": {:hex, :httpoison, "0.7.3"}, "idna": {:hex, :idna, "1.0.2"}, - "poison": {:hex, :poison, "1.4.0"}, - "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.4"}} + "poison": {:hex, :poison, "1.5.0"}, + "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}} -- cgit v1.2.1 From 271615ed72e3229581c625267f788a260bed88d9 Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 00:38:39 +0200 Subject: create a class dedicated to configuration --- lib/config.ex | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 lib/config.ex diff --git a/lib/config.ex b/lib/config.ex new file mode 100644 index 0000000..28a48d3 --- /dev/null +++ b/lib/config.ex @@ -0,0 +1,69 @@ +defmodule Mailchimp.Config do + use GenServer + + defstruct api_key: nil, api_version: "3.0" + + require Logger + + # Public API + + def start_link(%__MODULE__{}=config) do + Agent.start_link(fn -> config end, name: __MODULE__) + end + + def start_link do + config = %__MODULE__{ + api_key: get_api_key_from_config, + api_version: get_api_version_from_config + } + + Agent.start_link(fn -> config end, name: __MODULE__) + end + + def root_endpoint do + Agent.get(__MODULE__, fn %{api_key: api_key, api_version: api_version} -> + {:ok, shard} = get_shard(api_key) + "https://#{shard}.api.mailchimp.com/#{api_version}/" + end) + end + + def api_key do + Agent.get(__MODULE__, fn %{api_key: api_key} -> api_key end) + end + + def api_version do + Agent.get(__MODULE__, fn %{api_version: api_version} -> api_version end) + end + + def update(config) when is_list(config) do + Agent.update(__MODULE__, fn current_config -> + Enum.reduce(config, current_config, fn({k,v}, acc) -> Map.put(acc, k, v) end) + end) + end + + # Private methods + + defp get_api_key_from_config do + Application.get_env(:mailchimp, :apikey) || Application.get_env(:mailchimp, :api_key) + end + + defp get_api_version_from_config do + Application.get_env(:mailchimp, :api_version) || "3.0" + end + + defp get_shard(api_key) do + shard = api_key + |> String.split(~r{-}) + |> List.last + + case shard do + nil -> + Logger.error "[mailchimp] This doesn't look like an API Key: #{api_key}" + Logger.error "[mailchimp] The API Key should have both a key and a server name, separated by a dash, like this: abcdefg8abcdefg6abcdefg4-us1" + :error + + _ -> + {:ok, shard} + end + end +end -- cgit v1.2.1 From 39f3c3ab41d3c2c342a8ee70a3d734bbcd5b273e Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 00:42:16 +0200 Subject: refactor the http client --- lib/httpclient.ex | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/lib/httpclient.ex b/lib/httpclient.ex index 2b1594b..e34312f 100644 --- a/lib/httpclient.ex +++ b/lib/httpclient.ex @@ -1,31 +1,22 @@ defmodule Mailchimp.HTTPClient do + use HTTPoison.Base - def get(url, header) do - case HTTPoison.get(url, header) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - process_response_body body - {:ok, %HTTPoison.Response{status_code: 404}} -> - "Not found :(" - {:error, %HTTPoison.Error{reason: reason}} -> - reason - end - end + alias Mailchimp.Config - def post(url, body, header) do - case HTTPoison.post(url, body, header) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - process_response_body body - {:ok, %HTTPoison.Response{status_code: 404}} -> - "Not found :(" - {:error, %HTTPoison.Error{reason: reason}} -> - reason - end + def process_url(url) do + Config.root_endpoint <> url end def process_response_body(body) do - body - |> Poison.decode! - |> Enum.map(fn({k, v}) -> {String.to_atom(k), v} end) + Poison.decode!(body, keys: :atoms) end + def process_request_headers(headers) do + encoded_api = Base.encode64(":#{Config.api_key}") + + headers + |> Enum.into(%{}) + |> Map.put("Authorization", "Basic #{encoded_api}") + |> Enum.into([]) + end end -- cgit v1.2.1 From 818c1bb791a30a88df54c286a3cb1571845a3d27 Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 01:22:47 +0200 Subject: starts the config agent on application startup --- lib/mailchimp.ex | 32 ++++++++------------------------ mix.exs | 3 ++- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/lib/mailchimp.ex b/lib/mailchimp.ex index 144168d..fa8eb1d 100644 --- a/lib/mailchimp.ex +++ b/lib/mailchimp.ex @@ -1,16 +1,15 @@ defmodule Mailchimp do use Application - use GenServer - require Logger - @apikey Application.get_env :mailchimp, :apikey + def start(_type, _args) do + import Supervisor.Spec, warn: false - ### Public API - def start_link do - shard = get_shard - apiroot = "https://#{shard}.api.mailchimp.com/3.0/" - config = %{apiroot: apiroot, apikey: @apikey} - GenServer.start_link(Mailchimp, config, name: :mailchimp) + children = [ + worker(Mailchimp.Config, []), + ] + + opts = [strategy: :one_for_one, name: Aliver.Supervisor] + Supervisor.start_link(children, opts) end def get_account_details do @@ -49,19 +48,4 @@ defmodule Mailchimp do member = Mailchimp.List.add_member(config, %{"list_id" => list_id, "email" => email}) {:reply, member, config} end - - def get_shard do - parts = @apikey - |> String.split(~r{-}) - - case length(parts) do - 2 -> - List.last parts - _ -> - Logger.error "This doesn't look like an API Key: #{@apikey}" - Logger.info "The API Key should have both a key and a server name, separated by a dash, like this: abcdefg8abcdefg6abcdefg4-us1" - {:error} - end - end - end diff --git a/mix.exs b/mix.exs index 5a0e014..2883846 100644 --- a/mix.exs +++ b/mix.exs @@ -13,7 +13,8 @@ defmodule Mailchimp.Mixfile do end def application do - [applications: [:logger, :httpoison]] + [mod: {Mailchimp, []}, + applications: [:logger, :httpoison]] end defp description do -- cgit v1.2.1 From cedebfbae736e26d5d96129bb46dd868de8e1224 Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 02:49:52 +0200 Subject: refactor the account and list modules --- lib/account.ex | 41 ++++++++++++++++++++++++++++++++++----- lib/httpclient.ex | 7 ++++++- lib/link.ex | 20 +++++++++++++++++++ lib/list.ex | 58 ++++++++++++++++++++++--------------------------------- 4 files changed, 85 insertions(+), 41 deletions(-) create mode 100644 lib/link.ex diff --git a/lib/account.ex b/lib/account.ex index 586d274..7e24858 100644 --- a/lib/account.ex +++ b/lib/account.ex @@ -1,10 +1,41 @@ defmodule Mailchimp.Account do - import Mailchimp.HTTPClient + alias HTTPoison.Response + alias Mailchimp.HTTPClient + alias Mailchimp.Link + alias Mailchimp.List - def get_details(config) do - map_header = %{"Authorization" => "apikey #{config.apikey}"} - url = config.apiroot - get(url, map_header) + defstruct account_id: nil, account_name: nil, contact: nil, last_login: nil, total_subscribers: 0, links: [] + + def new(attributes) do + %__MODULE__{ + account_id: attributes[:account_id], + account_name: attributes[:account_name], + contact: attributes[:contact], + last_login: attributes[:last_login], + total_subscribers: attributes[:total_subscribers], + links: Link.get_links_from_attributes(attributes) + } + end + + def get do + {:ok, response} = HTTPClient.get("/") + case response do + %Response{status_code: 200, body: body} -> + {:ok, __MODULE__.new(body)} + + %Response{status_code: _, body: body} -> + {:error, body} + end end + def lists(%__MODULE__{links: %{"lists" => %Link{href: href}}}) do + {:ok, response} = HTTPClient.get(href) + case response do + %Response{status_code: 200, body: body} -> + {:ok, Enum.map(body.lists, &List.new(&1))} + + %Response{status_code: _, body: body} -> + {:error, body} + end + end end diff --git a/lib/httpclient.ex b/lib/httpclient.ex index e34312f..e84ac2d 100644 --- a/lib/httpclient.ex +++ b/lib/httpclient.ex @@ -4,7 +4,12 @@ defmodule Mailchimp.HTTPClient do alias Mailchimp.Config def process_url(url) do - Config.root_endpoint <> url + root_endpoint = Config.root_endpoint + if String.starts_with?(url, root_endpoint) do + url + else + root_endpoint <> url + end end def process_response_body(body) do diff --git a/lib/link.ex b/lib/link.ex new file mode 100644 index 0000000..fc355b9 --- /dev/null +++ b/lib/link.ex @@ -0,0 +1,20 @@ +defmodule Mailchimp.Link do + defstruct rel: nil, href: nil, method: nil, schema: nil, target_schema: nil + + def new(attributes) do + %__MODULE__{ + rel: attributes[:rel], + href: attributes[:href], + method: attributes[:method], + schema: attributes[:schema], + target_schema: attributes[:targetSchema] + } + end + + def get_links_from_attributes(attributes) do + (attributes._links || []) + |> Enum.map(&__MODULE__.new(&1)) + |> Enum.map(&({&1.rel, &1})) + |> Enum.into(%{}) + end +end diff --git a/lib/list.ex b/lib/list.ex index 43ec63a..4fff8a1 100644 --- a/lib/list.ex +++ b/lib/list.ex @@ -1,40 +1,28 @@ defmodule Mailchimp.List do - import Mailchimp.HTTPClient + alias Mailchimp.HTTPClient + alias Mailchimp.Link - def all(config) do - map_header = %{"Authorization" => "apikey #{config.apikey}"} - config.apiroot - |> build_url - |> get(map_header) - end - - def members(config, list_id) do - map_header = %{"Authorization" => "apikey #{config.apikey}"} - config.apiroot <> "lists/" <> list_id <> "/members" - |> get(map_header) - end - - def add_member(config, %{"list_id" => list_id, "email" => email}) do - map_header = %{"Authorization" => "apikey #{config.apikey}"} - {:ok, body} = Poison.encode(%{email_address: email, status: "subscribed"}) - config.apiroot <> "lists/" <> list_id <> "/members" - |> post(body, map_header) - end - - def build_url(root) do - params = [ - {:fields, ["lists.id", "lists.name", "lists.stats.member_count"]}, - {:count, 10}, - {:offset, 0} - ] - fields = "fields=" <> as_string(params[:fields]) - count = "count=" <> to_string params[:count] - offset = "offset=" <> to_string params[:offset] - url = root <> "lists?" <> fields <> "&" <> count <> "&" <> offset - end + defstruct id: nil, name: nil, contact: nil, permission_reminder: nil, use_archive_bar: nil, campaign_defaults: nil, notify_on_subscribe: nil, notify_on_unsubscribe: nil, list_rating: nil, email_type_option: nil, subscribe_url_short: nil, subscribe_url_long: nil, beamer_address: nil, visibility: nil, modules: [], stats: nil, links: [] - def as_string(param) do - Enum.reduce(param, fn(s, acc) -> acc<>","<>s end) + def new(attributes) do + %__MODULE__{ + id: attributes[:id], + name: attributes[:name], + contact: attributes[:contact], + permission_reminder: attributes[:permission_reminder], + use_archive_bar: attributes[:use_archive_bar], + campaign_defaults: attributes[:campaign_defaults], + notify_on_subscribe: attributes[:notify_on_subscribe], + notify_on_unsubscribe: attributes[:notify_on_unsubscribe], + list_rating: attributes[:list_rating], + email_type_option: attributes[:email_type_option], + subscribe_url_short: attributes[:subscribe_url_short], + subscribe_url_long: attributes[:subscribe_url_long], + beamer_address: attributes[:beamer_address], + visibility: attributes[:visibility], + modules: attributes[:modules], + stats: attributes[:stats], + links: Link.get_links_from_attributes(attributes) + } end - end -- cgit v1.2.1 From d46896cf276579618afb9b6ad46100f78b77bb25 Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 03:57:04 +0200 Subject: refactor the list members related apis --- lib/list.ex | 35 +++++++++++++++++++++++++++++++++++ lib/member.ex | 30 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 lib/member.ex diff --git a/lib/list.ex b/lib/list.ex index 4fff8a1..81853e4 100644 --- a/lib/list.ex +++ b/lib/list.ex @@ -1,6 +1,8 @@ defmodule Mailchimp.List do + alias HTTPoison.Response alias Mailchimp.HTTPClient alias Mailchimp.Link + alias Mailchimp.Member defstruct id: nil, name: nil, contact: nil, permission_reminder: nil, use_archive_bar: nil, campaign_defaults: nil, notify_on_subscribe: nil, notify_on_unsubscribe: nil, list_rating: nil, email_type_option: nil, subscribe_url_short: nil, subscribe_url_long: nil, beamer_address: nil, visibility: nil, modules: [], stats: nil, links: [] @@ -25,4 +27,37 @@ defmodule Mailchimp.List do links: Link.get_links_from_attributes(attributes) } end + + def members(%__MODULE__{links: %{"members" => %Link{href: href}}}) do + {:ok, response} = HTTPClient.get(href) + case response do + %Response{status_code: 200, body: body} -> + {:ok, Enum.map(body.members, &Member.new(&1))} + + %Response{status_code: _, body: body} -> + {:error, body} + end + end + + def create_member(%__MODULE__{links: %{"members" => %Link{href: href}}}, email_address, status, merge_fields \\ %{}) when is_binary(email_address) and is_map(merge_fields) and status in [:subscribed, :pending, :unsubscribed, :cleaned] do + {:ok, response} = HTTPClient.get(href) + case response do + %Response{status_code: 200, body: body} -> + links = Link.get_links_from_attributes(body) + href = links["create"].href + data = %{email_address: email_address, status: status, merge_fields: merge_fields} + + {:ok, response} = HTTPClient.post href, Poison.encode!(data) + case response do + %Response{status_code: 200, body: body} -> + {:ok, Member.new(body)} + + %Response{status_code: _, body: body} -> + {:error, body} + end + + %Response{status_code: _, body: body} -> + {:error, body} + end + end end diff --git a/lib/member.ex b/lib/member.ex new file mode 100644 index 0000000..3f49722 --- /dev/null +++ b/lib/member.ex @@ -0,0 +1,30 @@ +defmodule Mailchimp.Member do + alias Mailchimp.Link + + defstruct email_address: nil, email_client: nil, email_type: nil, id: nil, ip_opt: nil, ip_signup: nil, language: nil, last_changed: nil, list_id: nil, location: nil, member_rating: nil, merge_fields: nil, stats: nil, status: nil, status_if_new: nil, timestamp_opt: nil, timestamp_signup: nil, unique_email_id: nil, vip: nil + + def new(attributes) do + %{ + email_address: attributes[:email_address], + email_client: attributes[:email_client], + email_type: attributes[:email_type], + id: attributes[:id], + ip_opt: attributes[:ip_opt], + ip_signup: attributes[:ip_signup], + language: attributes[:language], + last_changed: attributes[:last_changed], + list_id: attributes[:list_id], + location: attributes[:location], + member_rating: attributes[:member_rating], + merge_fields: attributes[:merge_fields], + stats: attributes[:stats], + status: attributes[:status], + status_if_new: attributes[:status_if_new], + timestamp_opt: attributes[:timestamp_opt], + timestamp_signup: attributes[:timestamp_signup], + unique_email_id: attributes[:unique_email_id], + vip: attributes[:vip], + links: Link.get_links_from_attributes(attributes) + } + end +end -- cgit v1.2.1 From 8b77bf7207380748ba0370715b3123afff42aba7 Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 04:04:59 +0200 Subject: remove old apis from Mailchimp module --- lib/mailchimp.ex | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/lib/mailchimp.ex b/lib/mailchimp.ex index fa8eb1d..914c54b 100644 --- a/lib/mailchimp.ex +++ b/lib/mailchimp.ex @@ -11,41 +11,5 @@ defmodule Mailchimp do opts = [strategy: :one_for_one, name: Aliver.Supervisor] Supervisor.start_link(children, opts) end - - def get_account_details do - GenServer.call(:mailchimp, :account_details) - end - - def get_all_lists do - GenServer.call(:mailchimp, :all_lists) - end - - def get_list_members(list_id) do - GenServer.call(:mailchimp, {:list_members, list_id}) - end - - def add_member(list_id, email) do - GenServer.call(:mailchimp, {:add_member, list_id, email}) - end - - ### Server API - def handle_call(:account_details, _from, config) do - details = Mailchimp.Account.get_details(config) - {:reply, details, config} - end - - def handle_call(:all_lists, _from, config) do - lists = Mailchimp.List.all(config) - {:reply, lists, config} - end - - def handle_call({:list_members, list_id}, _from, config) do - members = Mailchimp.List.members(config, list_id) - {:reply, members, config} - end - - def handle_call({:add_member, list_id, email}, _from, config) do - member = Mailchimp.List.add_member(config, %{"list_id" => list_id, "email" => email}) - {:reply, member, config} - end end + -- cgit v1.2.1 From 691d286313961598b86a1801bba8d3b10e23dca6 Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 04:16:25 +0200 Subject: update the readme --- README.md | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a713b54..b07b0d6 100644 --- a/README.md +++ b/README.md @@ -21,27 +21,16 @@ def application do end ``` -## Usage +## API -1. Put your API key in your *config.exs* file: +Put your API key in your *config.exs* file: ```elixir config :mailchimp, apikey: "your api-us10" ``` +For now Mailchimp only supports HTTP Basic Auth. -2. Start a new process: +## Documentation - Mailchimp.start_link - -### Getting Account Details - - Mailchimp.get_account_details() - -### Getting All Lists - - Mailchimp.get_all_lists() - -### Adding a Member to a List - - Mailchimp.add_member("list_id", "e-mail") +TODO -- cgit v1.2.1 From 670aa48b4c88276f9ef68a733b5dd6150685777f Mon Sep 17 00:00:00 2001 From: Rawane Zossou Date: Sat, 19 Sep 2015 04:35:42 +0200 Subject: add support for env var in api key configuration --- lib/config.ex | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/config.ex b/lib/config.ex index 28a48d3..8778e5b 100644 --- a/lib/config.ex +++ b/lib/config.ex @@ -43,8 +43,16 @@ defmodule Mailchimp.Config do # Private methods + defp sanitize_api_key({:system, env_var}) do + sanitize_api_key System.get_env(env_var) + end + + defp sanitize_api_key(api_key) do + api_key + end + defp get_api_key_from_config do - Application.get_env(:mailchimp, :apikey) || Application.get_env(:mailchimp, :api_key) + sanitize_api_key(Application.get_env(:mailchimp, :apikey)) || sanitize_api_key(Application.get_env(:mailchimp, :api_key)) end defp get_api_version_from_config do -- cgit v1.2.1