diff --git a/.gitignore b/.gitignore index ad1a97f..b3d95e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ .idea/ sendgridelixir.iml +sengrid_elixir.iml +sendgrid.iml /_build /cover /deps erl_crash.dump *.ez /doc -.DS_Store \ No newline at end of file +.DS_Store diff --git a/lib/sendgrid/email.ex b/lib/sendgrid/email.ex index 954cb53..11975b5 100644 --- a/lib/sendgrid/email.ex +++ b/lib/sendgrid/email.ex @@ -33,7 +33,7 @@ defmodule SendGrid.Email do You can provide a Unix timestamp to have an email delivered in the future. - send_at(email, 1409348513) + put_send_at(email, 1409348513) ## Phoenix Views @@ -91,14 +91,22 @@ defmodule SendGrid.Email do bcc: nil, from: nil, reply_to: nil, + reply_to_list: nil, subject: nil, content: nil, template_id: nil, + version_id: nil, substitutions: nil, custom_args: nil, personalizations: nil, send_at: nil, headers: nil, + categories: nil, + batch_id: nil, + asm: nil, + ip_pool_name: nil, + mail_settings: nil, + tracking_settings: nil, attachments: nil, dynamic_template_data: nil, sandbox: false, @@ -111,15 +119,23 @@ defmodule SendGrid.Email do bcc: nil | [recipient], from: nil | recipient, reply_to: nil | recipient, + reply_to_list: nil | [recipient], subject: nil | String.t(), content: nil | [content], template_id: nil | String.t(), + version_id: nil | String.t(), substitutions: nil | substitutions, custom_args: nil | custom_args, personalizations: nil | [Personalization.t()], dynamic_template_data: nil | dynamic_template_data, send_at: nil | integer, headers: nil | headers(), + categories: nil | [String.t], + batch_id: nil | String.t, + asm: nil | asm(), + ip_pool_name: nil | String.t, + mail_settings: nil | mail_settings(), + tracking_settings: nil | tracking_settings(), attachments: nil | [attachment], sandbox: boolean(), __phoenix_view__: nil | atom, @@ -130,6 +146,18 @@ defmodule SendGrid.Email do @type recipient :: %{required(:email) => String.t(), optional(:name) => String.t()} @type content :: %{type: String.t(), value: String.t()} @type headers :: %{String.t() => String.t()} + @type asm :: %{ + required(:group_id) => integer, + optional(:groups_to_display) => [integer] + } + @type mail_settings :: %{ + optional(:bypass_list_management) => bypass_filter(), + optional(:bypass_spam_management) => bypass_filter(), + optional(:bypass_bounce_management) => bypass_filter(), + optional(:bypass_unsubscribe_management) => bypass_filter(), + optional(:footer) => footer(), + optional(:sandbox_mode) => enable_status() + } @type attachment :: %{ required(:content) => String.t(), optional(:type) => String.t(), @@ -138,10 +166,49 @@ defmodule SendGrid.Email do optional(:content_id) => String.t() } + @type enable_status ::%{ + required(:enable) => boolean + } + @type bypass_filter :: enable_status() + @type footer :: %{ + required(:enable) => boolean, + required(:text) => String.t, + required(:html) => String.t + } @type substitutions :: %{String.t() => String.t()} @type custom_args :: %{String.t() => String.t()} @type dynamic_template_data :: %{String.t() => String.t()} + @type tracking_settings :: %{ + optional(:click_tracking) => click_tracking(), + optional(:open_tracking) => open_tracking(), + optional(:subscription_tracking) => subscription_tracking(), + optional(:ganalytics) => google_analytics_tracking() + } + + @type click_tracking :: %{ + optional(:enable) => boolean, + optional(:enable_text) => boolean + } + @type open_tracking :: %{ + optional(:enable) => boolean, + optional(:substitution_tag) => nil | String.t + } + @type subscription_tracking :: %{ + optional(:enable) => boolean, + optional(:text) => String.t, + optional(:html) => String.t, + optional(:substitution_tag) => String.t + } + @type google_analytics_tracking :: %{ + optional(:enable) => boolean, + optional(:utm_source) => String.t, + optional(:utm_medium) => String.t, + optional(:utm_content) => String.t, + optional(:utm_campaign) => String.t + } + + @doc """ Builds an an empty email to compose on. @@ -285,6 +352,152 @@ defmodule SendGrid.Email do %Email{email | reply_to: address(reply_to_address, reply_to_name)} end + + + @doc """ + Sets the `reply_to_list` field for email. You may not use reply_to_list and reply_to at the same time. + """ + @spec put_reply_to_list(t, [String.t()]) :: t + def put_reply_to_list(%Email{} = email, reply_to_addresses) do + list = Enum.map(reply_to_addresses, fn(v) -> + case v do + {address, name} -> address(address, name) + address -> address(address) + end + end) + %Email{email | reply_to_list: list} + end + + @doc """ + Set/Replace email categories. + """ + def put_categories(%Email{} = email, categories) do + %Email{email| categories: categories} + end + + @doc """ + Add/set email category. + """ + def put_category(%Email{} = email, category) do + case email.categories do + nil -> %Email{email| categories: [category]} + v -> %Email{email| categories: Enum.uniq(v ++ [category])} + end + end + + @doc """ + Set batch_id + """ + def put_batch(%Email{} = email, value) do + %Email{email| batch_id: value} + end + + @doc """ + Set asm + """ + def put_asm(%Email{} = email, value) do + %Email{email| asm: value} + end + + + @doc """ + Set ip_pool_name + """ + def put_ip_pool(%Email{} = email, value) do + %Email{email| ip_pool_name: value} + end + + # Initialize mail_settings + defp init_mail_settings(%Email{mail_settings: nil} = email) do + %Email{email| mail_settings: %{}} + end + defp init_mail_settings(%Email{} = email), do: email + + @doc """ + Set email.mail_settings.bypass_list_management + """ + def configure_list_management_bypass(%Email{} = email, enable) do + init_mail_settings(email) + |> put_in([Access.key(:mail_settings), :bypass_list_management], %{enable: enable}) + end + + @doc """ + Set email.mail_settings.bypass_spam_management + """ + def configure_spam_management_bypass(%Email{} = email, enable) do + init_mail_settings(email) + |> put_in([Access.key(:mail_settings), :bypass_spam_management], %{enable: enable}) + end + + @doc """ + Set email.mail_settings.bypass_bounce_management + """ + def configure_bounce_management_bypass(%Email{} = email, enable) do + init_mail_settings(email) + |> put_in([Access.key(:mail_settings), :bypass_bounce_management], %{enable: enable}) + end + + @doc """ + Set email.mail_settings.bypass_unsubscribe_management + """ + def configure_unsubscribe_management_bypass(%Email{} = email, enable) do + init_mail_settings(email) + |> put_in([Access.key(:mail_settings), :bypass_unsubscribe_management], %{enable: enable}) + end + + @doc """ + Set email.mail_settings.bypass_unsubscribe_management + """ + def put_footer(%Email{} = email, footer) do + init_mail_settings(email) + |> put_in([Access.key(:mail_settings), :footer], footer) + end + + @doc """ + Set entire mail_settings field, replacing any previous settings. + """ + def put_mail_settings(%Email{} = email, value) do + %Email{mail_settings: value} + end + + + # Initialize track_settings + defp init_tracking_settings(%Email{tracking_settings: nil} = email) do + %Email{email| tracking_settings: %{}} + end + defp init_tracking_settings(%Email{} = email), do: email + + def configure_click_tracking(%Email{} = email, value) do + email + |> init_tracking_settings() + |> put_in([Access.key(:tracking_settings), :click_tracking], value) + end + + def configure_open_tracking(%Email{} = email, value) do + email + |> init_tracking_settings() + |> put_in([Access.key(:tracking_settings), :open_tracking], value) + end + + def configure_subscription_tracking(%Email{} = email, value) do + email + |> init_tracking_settings() + |> put_in([Access.key(:tracking_settings), :subscription_tracking], value) + end + + def configure_google_analytics(%Email{} = email, value) do + email + |> init_tracking_settings() + |> put_in([Access.key(:tracking_settings), :ganalytics], value) + end + + def put_tracking_settings(%Email{} = email, value) do + email + |> put_in([Access.key(:tracking_settings)], value) + end + + + @doc """ Sets the `subject` field for the email. @@ -359,11 +572,28 @@ defmodule SendGrid.Email do Email.put_template(%Email{}, "the_template_id") """ - @spec put_template(t, String.t()) :: t + @spec put_template(t, String.t() | SendGrid.Template) :: t + def put_template(%Email{} = email, template = %SendGrid.LegacyTemplate{}), do: put_template(email, template.id) + def put_template(%Email{} = email, template = %SendGrid.DynamicTemplate{}), do: put_template(email, template.id) def put_template(%Email{} = email, template_id) do %Email{email | template_id: template_id} end + + @doc """ + Uses a predefined SendGrid template version for the email. + ## Examples + + Email.put_template_version(%Email{}, "the_template_version_id") + + """ + @spec put_template_version(t, String.t()) :: t + def put_template_version(%Email{} = email, version_id) when is_bitstring(version_id) do + %Email{email | version_id: version_id} + end + + + @doc """ Adds a substitution value to be used with a template. @@ -653,7 +883,7 @@ defmodule SendGrid.Email do @doc """ Sets the email to be sent with sandbox mode enabled or disabled. - The sandbox mode will default to what is explicity configured with + The sandbox mode will default to what is explicitly configured with SendGrid's configuration. """ @spec set_sandbox(t(), boolean()) :: t() @@ -694,24 +924,72 @@ defmodule SendGrid.Email do end defimpl Jason.Encoder do + + defp conditional_insert(params, email, field, as_field \\ nil) do + cond do + v = Map.get(email, field) -> put_in(params, [as_field || field], v) + :else -> params + end + end + def encode(%Email{personalizations: [_ | _]} = email, opts) do params = %{ - personalizations: email.personalizations, - from: email.from, - subject: email.subject, - content: email.content, - reply_to: email.reply_to, - send_at: email.send_at, - template_id: email.template_id, - attachments: email.attachments, - headers: email.headers, - mail_settings: %{ - sandbox_mode: %{ - enable: Application.get_env(:sendgrid, :sandbox_enable, email.sandbox) - } - } - } - + personalizations: email.personalizations, + from: email.from, + subject: email.subject, + content: email.content, + send_at: email.send_at, + attachments: email.attachments, + headers: email.headers, + } + # Template + |> then( + fn(params) -> + cond do + email.template_id && email.version_id -> put_in(params, [:template_id], email.template_id <> "." <> email.version_id) + email.template_id -> put_in(params, [:template_id], email.template_id) + email.version_id -> raise ArgumentError, "You must specify template if specifying template version" + :else -> params + end + end) + # Reply List + |> then( + fn(params) -> + cond do + email.reply_to_list && email.reply_to -> + raise ArgumentError, "You may not set reply_to_list and reply_to at the same time." + v = email.reply_to -> put_in(params, [:reply_to], v) + v = email.reply_to_list -> put_in(params, [:reply_to_list], v) + :else -> params + end + end) + # Mail Settings + |> conditional_insert(email, :mail_settings) + # Track Settings + |> conditional_insert(email, :tracking_settings, :track_settings) + # Categories + |> conditional_insert(email, :categories) + # Batch + |> conditional_insert(email, :batch_id) + # ASM + |> conditional_insert(email, :asm) + # IP Pool + |> conditional_insert(email, :ip_pool_name) + # sandbox_mode + |> then( + fn(params) -> + # Insure partially populated. + params + |> update_in([:mail_settings], &(&1 || %{})) + |> update_in([:mail_settings, :sandbox_mode], &(&1 || %{})) + |> update_in([:mail_settings, :sandbox_mode, :enable], fn(p) -> + cond do + is_boolean(p) -> p + :else -> Application.get_env(:sendgrid, :sandbox_enable, email.sandbox) + end + end) + end) + Jason.Encode.map(params, opts) end diff --git a/lib/sendgrid/templates/dynamic_template.ex b/lib/sendgrid/templates/dynamic_template.ex new file mode 100644 index 0000000..f82c037 --- /dev/null +++ b/lib/sendgrid/templates/dynamic_template.ex @@ -0,0 +1,66 @@ +defmodule SendGrid.DynamicTemplate do + @moduledoc """ + Module to interact with transaction email templates. + """ + alias __MODULE__ + + + @derive Jason.Encoder + defstruct [ + id: nil, + name: nil, + updated_at: nil, + generation: nil, + versions: nil + ] + + @type t :: %DynamicTemplate{ + id: String.t, + name: String.t, + updated_at: DateTime.t | nil, + versions: [Versions.t] + } + + @spec new(Map.t, :json) :: Template.t + def new(json, :json) do + # versions. + versions = case json["versions"] do + nil -> [] + versions when is_list(versions) -> + for version <- versions do + SendGrid.Template.Version.new(version, :json) + end + end + + %DynamicTemplate{ + id: json["id"], + name: json["name"], + updated_at: reformat_sendgrid_date(json["updated_at"]), + versions: versions + } + end + + defp reformat_sendgrid_date(date = %DateTime{}), do: date + defp reformat_sendgrid_date(date) when is_bitstring(date) do + [a,b] = String.split(date) + case DateTime.from_iso8601("#{a} #{b}.0Z") do + {:ok, d, _} -> d + _ -> nil + end + end + defp reformat_sendgrid_date(_), do: nil + + + defimpl Jason.Encoder do + def encode(%SendGrid.DynamicTemplate{} = template, opts) do + raw = template + |> Map.from_struct() + |> Map.drop([:updated_at]) + |> Enum.filter(fn({_k,v}) -> v != nil end) + |> Map.new() + |> Map.put(:generation, :dynamic) + Jason.Encode.map(raw, opts) + end + end + +end diff --git a/lib/sendgrid/templates/legacy_template.ex b/lib/sendgrid/templates/legacy_template.ex new file mode 100644 index 0000000..eb4001f --- /dev/null +++ b/lib/sendgrid/templates/legacy_template.ex @@ -0,0 +1,66 @@ +defmodule SendGrid.LegacyTemplate do + @moduledoc """ + Module to interact with transaction email templates. + """ + alias __MODULE__ + + @derive Jason.Encoder + defstruct [ + id: nil, + name: nil, + updated_at: nil, + versions: nil + ] + + @type t :: %LegacyTemplate{ + id: String.t, + name: String.t, + updated_at: DateTime.t | nil, + versions: [Versions.t] + } + + @spec new(Map.t, :json) :: LegacyTemplate.t + def new(json, :json) do + # versions. + versions = case json["versions"] do + nil -> [] + versions when is_list(versions) -> + for version <- versions do + SendGrid.Template.Version.new(version, :json) + end + end + + %LegacyTemplate{ + id: json["id"], + name: json["name"], + updated_at: reformat_sendgrid_date(json["updated_at"]), + versions: versions + } + end + + defp reformat_sendgrid_date(date = %DateTime{}), do: date + defp reformat_sendgrid_date(date) when is_bitstring(date) do + [a,b] = String.split(date) + case DateTime.from_iso8601("#{a} #{b}.0Z") do + {:ok, d, _} -> d + _ -> nil + end + end + defp reformat_sendgrid_date(_), do: nil + + + defimpl Jason.Encoder do + def encode(%SendGrid.LegacyTemplate{} = template, opts) do + raw = template + |> Map.from_struct() + |> Map.drop([:updated_at]) + |> Enum.filter(fn({_k,v}) -> v != nil end) + |> Map.new() + |> Map.put(:generation, :legacy) + Jason.Encode.map(raw, opts) + end + end + + + +end diff --git a/lib/sendgrid/templates/metadata.ex b/lib/sendgrid/templates/metadata.ex new file mode 100644 index 0000000..aa94d83 --- /dev/null +++ b/lib/sendgrid/templates/metadata.ex @@ -0,0 +1,38 @@ +defmodule SendGrid.MetaData do + @moduledoc """ + Pagination Meta Details, + """ + + defstruct [ + self: nil, + next: nil, + count: nil, + options: nil, + ] + + @type t :: %SendGrid.MetaData{ + self: String.t | nil, + next: String.t | nil, + count: integer, + options: SendGrid.query(), + } + + @spec new(Map.t, SendGrid.query(), :json) :: SendGrid.MetaData.t | {:error, [String.t]} | {:error, String.t} + def new(json, options, :json) do + %__MODULE__{ + count: json["count"], + self: extract_page_token(json["self"]), + next: extract_page_token(json["next"]), + options: options + } + end + + defp extract_page_token(url) when is_bitstring(url) do + case Regex.run(~r/.*page_token=([^&]+)/, url) do + [_, m] -> m + _ -> nil + end + end + defp extract_page_token(_), do: nil + +end \ No newline at end of file diff --git a/lib/sendgrid/templates/template.ex b/lib/sendgrid/templates/template.ex new file mode 100644 index 0000000..7e51979 --- /dev/null +++ b/lib/sendgrid/templates/template.ex @@ -0,0 +1,12 @@ +defmodule SendGrid.Template do + + def new(%{"generation" => "dynamic"} = json, :json) do + SendGrid.DynamicTemplate.new(json, :json) + end + + + def new(%{"generation" => "legacy"} = json, :json) do + SendGrid.LegacyTemplate.new(json, :json) + end + +end \ No newline at end of file diff --git a/lib/sendgrid/templates/template/version.ex b/lib/sendgrid/templates/template/version.ex new file mode 100644 index 0000000..07aade6 --- /dev/null +++ b/lib/sendgrid/templates/template/version.ex @@ -0,0 +1,86 @@ +defmodule SendGrid.Template.Version do + @moduledoc """ + Module to interact with transaction email template versions. + """ + alias __MODULE__ + + @derive Jason.Encoder + defstruct [ + id: nil, + template_id: nil, + updated_at: nil, + thumbnail_url: nil, + warnings: nil, + active: nil, + name: nil, + + html_content: "", + plain_content: "", + generate_plain_content: nil, + subject: "", + editor: nil, + test_data: nil, + ] + + @type t :: %Version{ + id: String.t, + template_id: String.t, + updated_at: DateTime.t, + thumbnail_url: String.t, + warnings: list | nil, + active: integer | nil, + name: String.t, + + html_content: String.t, + plain_content: String.t, + generate_plain_content: boolean | nil, + subject: String.t, + editor: String.t | nil, + test_data: Map.t | nil, + } + + @spec new(Map.t, :json) :: Version.t + def new(json, :json) do + %Version{ + id: json["id"], + template_id: json["template_id"], + updated_at: reformat_sendgrid_date(json["updated_at"]), + thumbnail_url: json["thumbnail_url"], + warnings: json["warnings"], + active: json["active"], + name: json["name"], + # content only returned when specifically fetching entry by id. Will be null otherwise. + html_content: json["html_content"], + plain_content: json["plain_content"], + generate_plain_content: json["generate_plain_content"], + subject: json["subject"], + editor: json["editor"], + test_data: json["test_data"] + } + end + + defp reformat_sendgrid_date(date = %DateTime{}), do: date + defp reformat_sendgrid_date(date) when is_bitstring(date) do + [a,b] = String.split(date) + case DateTime.from_iso8601("#{a} #{b}.0Z") do + {:ok, d, _} -> d + _ -> nil + end + end + defp reformat_sendgrid_date(_), do: nil + + + + defimpl Jason.Encoder do + def encode(%SendGrid.Template.Version{} = version, opts) do + raw = version + |> Map.from_struct() + |> Map.drop([:updated_at, :editor, :thumbnail_url]) + |> Enum.filter(fn({_k,v}) -> v != nil end) + |> Map.new() + Jason.Encode.map(raw, opts) + end + end + + +end diff --git a/lib/sendgrid/templates/template/versions.ex b/lib/sendgrid/templates/template/versions.ex new file mode 100644 index 0000000..aaaa9dc --- /dev/null +++ b/lib/sendgrid/templates/template/versions.ex @@ -0,0 +1,72 @@ +defmodule SendGrid.Template.Versions do + alias SendGrid.Template.Version + + @success_codes [200,201,202,203,204] + + @spec activate(Version.t, SendGrid.query()) :: Version.t | {:error, [String.t]} | {:error, String.t} + def activate(%Version{} = version, options \\ []) do + case SendGrid.post(base_url(version.template_id, version.id) <> "/activate", version, options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + Version.new(response.body, :json) + { :ok, %SendGrid.Response{ body: body } } -> { :error, body["errors"] || body["error"]} + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + + @spec get(String.t, String.t, SendGrid.query()) :: Version.t | {:error, [String.t]} | {:error, String.t} + def get(template, version, options \\ []) do + case SendGrid.get(base_url(template,version), options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + Version.new(response.body, :json) + { :ok, %SendGrid.Response{ body: body } } -> + { :error, body["errors"] || body["error"] } + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + + @spec update(Version.t, SendGrid.query()) :: Version.t | {:error, [String.t]} | {:error, String.t} + def update(%Version{} = version, options \\ []) do + case SendGrid.patch(base_url(version.template_id, version.id), version, options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + response = Version.new(response.body, :json) + put_in(response, [Access.key(:template_id)], version.template_id) + { :ok, %SendGrid.Response{ body: body } } -> { :error, body["errors"] || body["error"] } + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + + @spec create(Version.t, SnedGrid.query()) :: Version.t | {:error, [String.t]} | {:error, String.t} + def create(%Version{} = version, options \\ []) do + case SendGrid.post(base_url(version.template_id), version, options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + response = Version.new(response.body, :json) + if response.id != nil do + get(version.template_id, response.id) + else + {:error, :post_create_fetch_failure} + end + + { :ok, %SendGrid.Response{ body: body } } -> { :error, body["errors"] || body["error"]} + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + + @spec delete(Version.t, SendGrid.query()) :: :ok | {:error, [String.t]} | {:error, String.t} + def delete(%Version{} = version, options \\ []) do + case SendGrid.delete(base_url(version.template_id, version.id), options) do + { :ok, %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + :ok + { :ok, %SendGrid.Response{ body: body } } -> { :error, body["errors"] || body["error"] } + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + + defp base_url(template) do + "/v3/templates/#{template}/versions" + end + + defp base_url(template, version) do + "/v3/templates/#{template}/versions/#{version}" + end + +end diff --git a/lib/sendgrid/templates/templates.ex b/lib/sendgrid/templates/templates.ex new file mode 100644 index 0000000..eacd6af --- /dev/null +++ b/lib/sendgrid/templates/templates.ex @@ -0,0 +1,181 @@ +defmodule SendGrid.Templates do + alias SendGrid.Template + @base_api_url "/v3/templates" + @valid_generations [:legacy, :dynamic] + @success_codes [200,201,202,203,204] + + #---------------------------------------- + # Result Set + #---------------------------------------- + defstruct [ + templates: [], + metadata: nil, + ] + + @type t :: %SendGrid.Templates{ + templates: [SendGrid.Template.t], + metadata: SendGrid.MetaData.t | nil, + } + + @spec new(SendGrid.Response.t, SendGrid.query()) :: Templates.t | {:error, [String.t]} | {:error, String.t} + def new(%SendGrid.Response{body: %{"_metadata" => metadata, "result" => result}}, options) do + %__MODULE__{ + templates: Enum.map(result, &(SendGrid.Template.new(&1, :json))), + metadata: SendGrid.MetaData.new(metadata, options, :json) + } + end + def new(%SendGrid.Response{body: %{"templates" => templates}}, options) do + %__MODULE__{ + templates: Enum.map(templates, &(SendGrid.Template.new(&1, :json))), + metadata: SendGrid.MetaData.new(%{}, options, :json) + } + end + def new(_), do: {:error, "#{__MODULE__} Unsupported Initializer"} + + #---------------------------------------- + # Pagination + #---------------------------------------- + @spec next(SendGrid.Templates.t, SendGrid.query()) :: Templates.t | {:error, [String.t]} | {:error, String.t} + def next(self, options \\ []) + def next(%SendGrid.Templates{metadata: %SendGrid.MetaData{next: nil}}, options) do + nil + end + def next(%SendGrid.Templates{} = self, options) do + # Note only api_key may be changed when calling next + options = cond do + api_key = options[:api_key] -> Keyword.put(self.metadata.options || [], :api_key, api_key) + :else -> self.metadata.options || [] + end + options = cond do + query = options[:query] -> + query = Keyword.put(query, :page_token, self.metadata.next) + Keyword.put(options, :query, query) + :else -> + Keyword.put(options, :query, [page_token: self.metadata.next]) + end + fetch = SendGrid.get(@base_api_url, options) + case fetch do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + __MODULE__.new(response, options) + { :ok, %SendGrid.Response{ body: body } } -> + { :error, body["errors"] || body["error"] } + _ -> + { :error, "Unable to communicate with SendGrid API." } + end + end + + #---------------------------------------- + # CRUD + #---------------------------------------- + @spec get(String.t, SendGrid.query()) :: SendGrid.DynamicTemplate.t | SendGrid.LegacyTemplate.t | {:error, [String.t]} | {:error, String.t} + def get(identifier, options \\ []) do + options = patch_options(options) + case SendGrid.get(@base_api_url <> "/#{identifier}", options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + SendGrid.Template.new(response.body, :json) + { :ok, %SendGrid.Response{ body: body } } -> { :error, body["errors"] || body["error"] } + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + + @spec update(SendGrid.LegacyTemplate.t | SendGrid.DynamicTemplate.t, SendGrid.query()) :: SendGrid.DynamicTemplate.t | SendGrid.LegacyTemplate.t | {:error, [String.t]} | {:error, String.t} + def update(template, options \\ []) + def update(%SendGrid.LegacyTemplate{} = template, options) do + options = patch_options(options) + case SendGrid.patch(@base_api_url <> "/#{template.id}", template, options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + SendGrid.Template.new(response.body, :json) + { :ok, %SendGrid.Response{ body: body } } -> + { :error, body["errors"] || body["error"] } + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + def update(%SendGrid.DynamicTemplate{} = template, options) do + options = patch_options(options) + case SendGrid.patch(@base_api_url <> "/#{template.id}", template, options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + SendGrid.Template.new(response.body, :json) + { :ok, %SendGrid.Response{ body: body } } -> + { :error, body["errors"] || body["error"] } + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + + @spec create(SendGrid.LegacyTemplate.t | SendGrid.DynamicTemplate.t, SendGrid.query()) :: SendGrid.DynamicTemplate.t | SendGrid.LegacyTemplate.t | {:error, [String.t]} | {:error, String.t} + def create(template, options \\ []) + def create(%SendGrid.LegacyTemplate{} = template, options) do + options = patch_options(options) + case SendGrid.post(@base_api_url, template, options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + SendGrid.Template.new(response.body, :json) + { :ok, %SendGrid.Response{ body: body }} -> + { :error, body["errors"] || body["error"] } + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + def create(%SendGrid.DynamicTemplate{} = template, options) do + options = patch_options(options) + case SendGrid.post(@base_api_url, template, options) do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + SendGrid.Template.new(response.body, :json) + { :ok, %SendGrid.Response{ body: body }} -> + { :error, body["errors"] || body["error"] } + _ -> { :error, "Unable to communicate with SendGrid API." } + end + end + + @spec delete(String.t | SendGrid.DynamicTemplate.t | Sendgrid.LegacyTemplate.t, SendGrid.query()) :: :ok | {:error, [String.t]} | {:error, String.t} + def delete(template, options \\ []) + def delete(%SendGrid.LegacyTemplate{id: id}, options) do + delete(id, options) + end + def delete(%SendGrid.DynamicTemplate{id: id}, options) do + delete(id, options) + end + def delete(identifier, options) when is_bitstring(identifier) do + options = patch_options(options) + case SendGrid.delete(@base_api_url <> "/#{identifier}", options) do + { :ok, %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + :ok + { :ok, %SendGrid.Response{ body: body } } -> + { :error, body["errors"] || body["error"] } + _ -> + { :error, "Unable to communicate with SendGrid API." } + end + end + + @spec list(SendGrid.query()) :: SendGrid.Templates.t | {:error, [String.t]} | {:error, String.t} + def list(options \\ []) do + options = patch_options(options) + fetch = SendGrid.get(@base_api_url, options) + case fetch do + { :ok, response = %SendGrid.Response{ status: status_code } } when status_code in @success_codes -> + __MODULE__.new(response, options) + { :ok, %SendGrid.Response{ body: body } } -> + { :error, body["errors"] || body["error"] } + _ -> + { :error, "Unable to communicate with SendGrid API." } + end + end + + #---------------------------------------- + # Support + #---------------------------------------- + @doc """ + Param injector does not handle list data, overriding here to allow user to pass in array or desired generations. + """ + @spec patch_options(SendGrid.query()) :: SendGrid.query() + def patch_options(options \\ []) do + case options[:query][:generations] do + v when is_list(v) -> + generations = v + |> Enum.map(&("#{&1}")) + |> Enum.join(",") + put_in(options, [:query, :generations], generations) + v when is_atom(v) -> options + v when is_bitstring(v) -> options + _else -> options + end + end + +end \ No newline at end of file diff --git a/mix.exs b/mix.exs index 11df5f5..3e2c8fa 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule SendGrid.Mixfile do def project do [app: :sendgrid, - version: "2.0.0", + version: "2.0.1", elixir: "~> 1.4", package: package(), compilers: compilers(Mix.env), @@ -45,8 +45,8 @@ defmodule SendGrid.Mixfile do {:earmark, "~> 1.2", only: :dev}, {:ex_doc, "~> 0.19", only: :dev}, {:jason, "~> 1.1"}, - {:phoenix, "~> 1.2", only: :test}, - {:phoenix_html, "~> 2.9", only: :test}, + {:phoenix, "~> 1.6", only: :test}, + {:phoenix_html, "~> 3.2", only: :test}, {:tesla, "~> 1.2"} ] end diff --git a/mix.lock b/mix.lock index 8fc0c58..4621c14 100644 --- a/mix.lock +++ b/mix.lock @@ -1,27 +1,32 @@ %{ + "castore": {:hex, :castore, "0.1.20", "62a0126cbb7cb3e259257827b9190f88316eb7aa3fdac01fd6f2dfd64e7f46e9", [:mix], [], "hexpm", "a020b7650529c986c454a4035b6b13a328e288466986307bea3aadb4c95ac98a"}, "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], []}, - "dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, - "erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "4bba10c6f267a0dd127d687d1295f6a11af6a7f160cc0e261c46f1962a98d7d8"}, + "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm", "b42a23e9bd92d65d16db2f75553982e58519054095356a418bb8320bbacb58b1"}, + "erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm", "f9388f7d1a668bee6ebddc040422ed6340af74aced153e492330da4c39516d92"}, + "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "dc87f778d8260da0189a622f62790f6202af72f2f3dee6e78d91a18dd2fcd137"}, "floki": {:hex, :floki, "0.17.2", "81b3a39d85f5cae39c8da16236ce152f7f8f50faf84b480ba53351d7e96ca6ca", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, optional: false]}]}, "hackney": {:hex, :hackney, "1.6.5", "8c025ee397ac94a184b0743c73b33b96465e85f90a02e210e86df6cbafaa5065", [:rebar3], [{:certifi, "0.7.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]}, "httpoison": {:hex, :httpoison, "0.11.0", "b9240a9c44fc46fcd8618d17898859ba09a3c1b47210b74316c0ffef10735e76", [:mix], [{:hackney, "~> 1.6.3", [hex: :hackney, optional: false]}]}, "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []}, - "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, - "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, + "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d7152ff93f2eac07905f510dfa03397134345ba4673a00fbf7119bab98632940"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "4a36dd2d0d5c5f98d95b3f410d7071cd661d5af310472229dd0e92161f168a44"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, - "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []}, + "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], []}, - "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, - "phoenix": {:hex, :phoenix, "1.2.4", "4172479b5e21806a5e4175b54820c239e0d4effb0b07912e631aa31213a05bae", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.4 or ~> 1.3.3 or ~> 1.2.4 or ~> 1.1.8 or ~> 1.0.5", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, - "phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], []}, - "plug": {:hex, :plug, "1.3.5", "7503bfcd7091df2a9761ef8cecea666d1f2cc454cbbaf0afa0b6e259203b7031", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, - "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, + "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm", "ebb595e19456a72786db6dcd370d320350cb624f0b6203fcc7e23161d49b0ffb"}, + "phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"}, + "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, + "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, + "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], [], "hexpm", "519bc209e4433961284174c497c8524c001e285b79bdf80212b47a1f898084cc"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}, - "tesla": {:hex, :tesla, "1.2.0", "9e2469c1bcdb0cc8fe5fd3e9208432f3fee8e439a44f639d8ce72bcccd6f3566", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, + "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, + "tesla": {:hex, :tesla, "1.2.0", "9e2469c1bcdb0cc8fe5fd3e9208432f3fee8e439a44f639d8ce72bcccd6f3566", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "9d6f9957d90ff2fdd370d62a3704b101dc0d11be73994b3794a43c37f01c1489"}, } diff --git a/test/support/templates/email/layout.html.eex b/test/support/templates/email/layout.html.eex index fab2a07..250b991 100644 --- a/test/support/templates/email/layout.html.eex +++ b/test/support/templates/email/layout.html.eex @@ -1,2 +1,2 @@ HTML LAYOUT -<%= render @view_module, @view_template, assigns %> \ No newline at end of file +<%= @inner_content %> \ No newline at end of file diff --git a/test/support/templates/email/layout.txt.eex b/test/support/templates/email/layout.txt.eex index 459f4a3..00cd2d2 100644 --- a/test/support/templates/email/layout.txt.eex +++ b/test/support/templates/email/layout.txt.eex @@ -1,2 +1,2 @@ TEXT LAYOUT -<%= render @view_module, @view_template, assigns %> \ No newline at end of file +<%= @inner_content %> \ No newline at end of file diff --git a/test/templates_test.exs b/test/templates_test.exs new file mode 100644 index 0000000..d150854 --- /dev/null +++ b/test/templates_test.exs @@ -0,0 +1,168 @@ +defmodule SendGrid.Templates.Test do + @moduledoc """ + Module to test Transactional Template CRUD. Not this module requires that api key has read/write/update/delete permissions for templates and versions. + """ + use ExUnit.Case, async: true + doctest SendGrid.Templates, import: true + doctest SendGrid.Template, import: true + doctest SendGrid.Template.Version, import: true + require Logger + alias SendGrid.Templates + alias SendGrid.Template + alias SendGrid.Template.Versions + alias SendGrid.Template.Version + alias SendGrid.DynamicTemplate + alias SendGrid.LegacyTemplate + + @tag :templates + test "generations query param patch" do + options = [query: [generations: [:legacy,:dynamic]]] + sut = Templates.patch_options(options) + assert sut == [query: [generations: "legacy,dynamic"]] + end + + @tag :templates + test "fetch templates" do + test_run = :os.system_time(:millisecond) + fixture_a = Templates.create(%LegacyTemplate{name: "TestTemplate#{test_run}a"}) + fixture_b = Templates.create(%DynamicTemplate{name: "TestTemplate#{test_run}b"}) + try do + actual = Templates.list(query: [page_size: 1]) + assert %Templates{} = actual + assert length(actual.templates) == 1 + assert is_bitstring(actual.metadata.self) + assert actual.metadata.count > 1 + after + Templates.delete(fixture_a) + Templates.delete(fixture_b) + end + end + + @tag :templates + test "paginated fetch templates" do + test_run = :os.system_time(:millisecond) + fixture_a = Templates.create(%LegacyTemplate{name: "TestTemplate#{test_run}a"}) + fixture_b = Templates.create(%DynamicTemplate{name: "TestTemplate#{test_run}b"}) + try do + actual = Templates.list(query: [page_size: 1]) + assert %Templates{} = actual + assert length(actual.templates) == 1 + assert is_bitstring(actual.metadata.self) + assert actual.metadata.count > 1 + + next = Templates.next(actual) + assert %Templates{} = actual + assert length(next.templates) == 1 + assert next.metadata.self != actual.metadata.self + assert actual.metadata.count > 1 + after + Templates.delete(fixture_a) + Templates.delete(fixture_b) + end + end + + + @tag :templates + test "template crud" do + # Note Better fixture management needed to avoid orphaned elements. + # Note I'm not a fan of multi asserts per test but it simplifies fixture management for now. + + test_run = :os.system_time(:millisecond) + name = "TestTemplate#{test_run}" + updated_name = "UpdatedTemplateName#{test_run}" + + # Create Template + new_template = Templates.create(%DynamicTemplate{name: name}) + assert new_template.id != nil + + try do + # Update Template + _updated_template = Templates.update(%DynamicTemplate{new_template| name: updated_name}) + + # Read Template + read_template = Templates.get(new_template.id) + assert read_template.name == updated_name + after + # Delete Template + Templates.delete(new_template) + end + {:error, _} = Templates.get(new_template.id) + end + + @tag :templates + test "template.version crud (legacy)" do + # Note Better fixture management needed to avoid orphaned elements. + # Note I'm not a fan of multi asserts per test but it simplifies fixture management for now. + + test_run = :os.system_time(:millisecond) + name = "TestTemplate#{test_run}" + test_version_name = "TestVersion#{test_run}" + updated_test_version = "TestVersionUpdated#{test_run}" + + # Create Template + new_template = Templates.create(%LegacyTemplate{name: name}) + template_id = new_template.id + assert template_id != nil + + try do + # Create Version + new_version = Versions.create(%Version{name: test_version_name, template_id: template_id, subject: "Hello World", html_content: "Hello", plain_content: "Hello txt"}) + assert new_version.id != nil + + # Update Version + updated_version = Versions.update(%Version{new_version| name: updated_test_version, editor: nil}) + # Get Version + read_version = Versions.get(updated_version.template_id, updated_version.id) + assert read_version.name == updated_test_version + + # Delete Version & Confirm + delete_version = Versions.delete(read_version) + assert delete_version == :ok + {:error, _details} = Versions.get(new_version.template_id, new_version.id) + after + Templates.delete(new_template) + end + + {:error, _} = Templates.get(new_template.id) + end + + + @tag :templates + test "template.version crud (dynamic)" do + # Note Better fixture management needed to avoid orphaned elements. + # Note I'm not a fan of multi asserts per test but it simplifies fixture management for now. + + test_run = :os.system_time(:millisecond) + name = "TestTemplate#{test_run}" + test_version_name = "TestVersion#{test_run}" + updated_test_version = "TestVersionUpdated#{test_run}" + + # Create Template + new_template = Templates.create(%DynamicTemplate{name: name}) + template_id = new_template.id + assert template_id != nil + + try do + # Create Version + new_version = Versions.create(%Version{name: test_version_name, template_id: template_id, subject: "Hello World", html_content: "Hello", plain_content: "Hello txt"}) + assert new_version.id != nil + + # Update Version + updated_version = Versions.update(%Version{new_version| name: updated_test_version, editor: nil}) + # Get Version + read_version = Versions.get(updated_version.template_id, updated_version.id) + assert read_version.name == updated_test_version + + # Delete Version & Confirm + delete_version = Versions.delete(read_version) + assert delete_version == :ok + {:error, _details} = Versions.get(new_version.template_id, new_version.id) + after + Templates.delete(new_template) + end + + {:error, _} = Templates.get(new_template.id) + end + + +end