diff --git a/circle.yml b/circle.yml index 9b94af194..adf83188b 100644 --- a/circle.yml +++ b/circle.yml @@ -14,8 +14,11 @@ dependencies: test: override: - | - if [ ${CIRCLE_PR_USERNAME} ]; then MIX_ENV=test mix test; else MIX_ENV=test mix coveralls.circle --include requires_env; fi - + if [ ${CIRCLE_PR_USERNAME} ]; then + MIX_ENV=test mix test; + else + MIX_ENV=test mix coveralls.circle --include requires_env; + fi post: - mix inch.report diff --git a/lib/code_corps/emails/receipt_email.ex b/lib/code_corps/emails/receipt_email.ex index 15a3e75f2..a523b7dde 100644 --- a/lib/code_corps/emails/receipt_email.ex +++ b/lib/code_corps/emails/receipt_email.ex @@ -13,7 +13,7 @@ defmodule CodeCorps.Emails.ReceiptEmail do do BaseEmail.create |> to(charge.user.email) - |> template(template_id, template_model) + |> template(template_id(), template_model) else nil -> {:error, :project_not_found} other -> other @@ -49,7 +49,7 @@ defmodule CodeCorps.Emails.ReceiptEmail do %{ charge_amount: charge.amount |> format_amount(), charge_statement_descriptor: charge.statement_descriptor, - high_five_image_url: high_five_image_url, + high_five_image_url: high_five_image_url(), project_current_donation_goal_description: current_donation_goal.description, project_title: project.title, project_url: project |> url(), @@ -62,7 +62,7 @@ defmodule CodeCorps.Emails.ReceiptEmail do "Your monthly donation to " <> project.title end - defp high_five_image_url, do: Enum.random(high_five_image_urls) + defp high_five_image_url, do: Enum.random(high_five_image_urls()) defp high_five_image_urls, do: [ "https://d3pgew4wbk2vb1.cloudfront.net/emails/images/emoji-1f64c-1f3fb@2x.png", diff --git a/lib/code_corps/helpers/policy.ex b/lib/code_corps/helpers/policy.ex index 4a73c246e..880e6cb62 100644 --- a/lib/code_corps/helpers/policy.ex +++ b/lib/code_corps/helpers/policy.ex @@ -46,7 +46,7 @@ defmodule CodeCorps.Helpers.Policy do Returns `:string` """ - @spec get_role(nil | OrganizationMembership.t | Changeset.t) :: String.t + @spec get_role(nil | OrganizationMembership.t | Changeset.t) :: String.t | nil def get_role(nil), do: nil def get_role(%OrganizationMembership{role: role}), do: role def get_role(%Changeset{} = changeset), do: changeset |> Changeset.get_field(:role) diff --git a/lib/code_corps/repo.ex b/lib/code_corps/repo.ex index f7686dc9a..fdcf466ec 100644 --- a/lib/code_corps/repo.ex +++ b/lib/code_corps/repo.ex @@ -1,4 +1,6 @@ defmodule CodeCorps.Repo do use Ecto.Repo, otp_app: :code_corps use Scrivener, page_size: 10 + + @dialyzer {:nowarn_function, rollback: 1} end diff --git a/lib/code_corps/services/donation_goals.ex b/lib/code_corps/services/donation_goals.ex index cccd6ad3f..012a9f5bf 100644 --- a/lib/code_corps/services/donation_goals.ex +++ b/lib/code_corps/services/donation_goals.ex @@ -15,6 +15,13 @@ defmodule CodeCorps.Services.DonationGoalsService do alias CodeCorps.{DonationGoal, Project, Repo} alias Ecto.Multi + # Prevents warning for calling `Repo.transaction(multi)`. + # The warning was caused with how the function is internally + # implemented, so there's no way around it + # As we update Ecto, we should check if this is still necessary. + # Last check was Ecto 2.1.3 + @dialyzer :no_opaque + @doc """ Creates the `CodeCorps.DonationGoal` by wrapping the following in a transaction: @@ -22,6 +29,7 @@ defmodule CodeCorps.Services.DonationGoalsService do - Inserting the donation goal - Updating the sibling goals with `update_related_goals/1` """ + @spec create(map) :: tuple def create(attributes) do changeset = %DonationGoal{} diff --git a/lib/code_corps/services/markdown_renderer.ex b/lib/code_corps/services/markdown_renderer.ex index 29b78c0ef..a0ec4c6b2 100644 --- a/lib/code_corps/services/markdown_renderer.ex +++ b/lib/code_corps/services/markdown_renderer.ex @@ -1,25 +1,27 @@ defmodule CodeCorps.Services.MarkdownRendererService do + @moduledoc """ + Used to render provided markdown into html using an external renderer package. + """ + + alias Ecto.Changeset + + @spec render_markdown_to_html(Changeset.t, atom, atom) :: Changeset.t + def render_markdown_to_html(%Changeset{valid?: false} = changeset, _, _), do: changeset def render_markdown_to_html(changeset, source_field, destination_field) do - case changeset do - %Ecto.Changeset{valid?: false} -> - changeset - %Ecto.Changeset{changes: %{^source_field => _}} -> - changeset - |> do_render_markdown_to_html(source_field, destination_field) - _ -> - changeset + case Changeset.get_change(changeset, source_field) do + nil -> changeset + markdown -> markdown |> convert_into_html() |> put_into(changeset, destination_field) end end - defp do_render_markdown_to_html(changeset, source_field, destination_field) do - markdown = - changeset - |> Ecto.Changeset.get_change(source_field) - {html, _errors} = - markdown - |> Earmark.as_html() + @spec convert_into_html(String.t) :: String.t + defp convert_into_html(markdown) do + # `Earmark.as_html` will return `{html, errors}` + markdown |> Earmark.as_html() |> Tuple.to_list() |> List.first() + end - changeset - |> Ecto.Changeset.put_change(destination_field, html) + @spec put_into(String.t, Changeset.t, atom) :: Changeset.t + defp put_into(html, changeset, destination_field) do + Ecto.Changeset.put_change(changeset, destination_field, html) end end diff --git a/lib/code_corps/services/user_service.ex b/lib/code_corps/services/user_service.ex index edf5f180f..4b298a68f 100644 --- a/lib/code_corps/services/user_service.ex +++ b/lib/code_corps/services/user_service.ex @@ -10,8 +10,14 @@ defmodule CodeCorps.Services.UserService do alias CodeCorps.{Repo, StripeConnectCustomer, StripePlatformCustomer, User} alias CodeCorps.StripeService.{StripeConnectCustomerService, StripePlatformCustomerService} - alias Ecto.Changeset - alias Ecto.Multi + alias Ecto.{Changeset, Multi} + + # Prevents warning for calling `Repo.transaction(multi)`. + # The warning was caused with how the function is internally + # implemented, so there's no way around it + # As we update Ecto, we should check if this is still necessary. + # Last check was Ecto 2.1.3 + @dialyzer :no_opaque @doc """ Updates a `CodeCorps.User` record and, if necessary, associated diff --git a/lib/code_corps/stripe_service/stripe_connect_account_service.ex b/lib/code_corps/stripe_service/stripe_connect_account_service.ex index eee25c1c9..3a9f1e671 100644 --- a/lib/code_corps/stripe_service/stripe_connect_account_service.ex +++ b/lib/code_corps/stripe_service/stripe_connect_account_service.ex @@ -8,6 +8,13 @@ defmodule CodeCorps.StripeService.StripeConnectAccountService do @api Application.get_env(:code_corps, :stripe) + # Prevents warning for calling `Repo.transaction(multi)`. + # The warning was caused with how the function is internally + # implemented, so there's no way around it + # As we update Ecto, we should check if this is still necessary. + # Last check was Ecto 2.1.3 + @dialyzer :no_opaque + @doc """ Used to create a remote `Stripe.Account` record as well as an associated local `StripeConnectAccount` record. diff --git a/lib/code_corps/stripe_service/stripe_connect_external_account_service.ex b/lib/code_corps/stripe_service/stripe_connect_external_account_service.ex index 628b667cb..0718817af 100644 --- a/lib/code_corps/stripe_service/stripe_connect_external_account_service.ex +++ b/lib/code_corps/stripe_service/stripe_connect_external_account_service.ex @@ -1,9 +1,12 @@ defmodule CodeCorps.StripeService.StripeConnectExternalAccountService do + @moduledoc """ + Used to perform actions on a `StripeConnectExternalAccount` record while + propagating to and from the associated `Stripe.ExternalAccount` record. + """ alias CodeCorps.{Repo, StripeConnectAccount, StripeExternalAccount} alias CodeCorps.StripeService.Adapters.StripeExternalAccountAdapter - @api Application.get_env(:code_corps, :stripe) - + @spec create(Stripe.ExternalAccount.t, StripeConnectAccount.t) :: {:ok, StripeExternalAccount.t} def create(%Stripe.ExternalAccount{} = external_account, %StripeConnectAccount{} = connect_account) do with {:ok, params} <- StripeExternalAccountAdapter.to_params(external_account, connect_account) do %StripeExternalAccount{} |> StripeExternalAccount.changeset(params) |> Repo.insert diff --git a/lib/code_corps/stripe_service/stripe_connect_plan.ex b/lib/code_corps/stripe_service/stripe_connect_plan.ex index ac9ea0bc1..77af41246 100644 --- a/lib/code_corps/stripe_service/stripe_connect_plan.ex +++ b/lib/code_corps/stripe_service/stripe_connect_plan.ex @@ -1,4 +1,10 @@ defmodule CodeCorps.StripeService.StripeConnectPlanService do + @moduledoc """ + Used to perform actions on `StripeConnectPlan` records + while at the same time propagating to and from associated `Stripe.Plan` + records. + """ + alias CodeCorps.{Project, Repo, StripeConnectPlan} alias CodeCorps.StripeService.Adapters.StripeConnectPlanAdapter alias CodeCorps.StripeService.Validators.ProjectCanEnableDonations @@ -8,17 +14,15 @@ defmodule CodeCorps.StripeService.StripeConnectPlanService do @doc """ Creates a new `Stripe.Plan` record on Stripe API, as well as an associated local `StripeConnectPlan` record - - # Possible return values - - - `{:ok, %StripeConnectPlan{}}` - the created record. - - `{:error, %Ecto.Changeset{}}` - the record was not created due to validation issues. - - `{:error, :project_not_ready}` - the associated project does not meed the prerequisites for creating a plan. - - `{:error, %Stripe.APIErrorResponse{}}` - there was a problem with the stripe request - - `{:error, :not_found}` - one of the associated records was not found """ + @spec create(map) :: {:ok, StripeConnectPlan.t} | + {:error, Ecto.Changeset.t} | + {:error, Stripe.APIErrorResponse.t} | + {:error, :project_not_ready} | + {:error, :not_found} def create(%{"project_id" => project_id} = attributes) do - with {:ok, %Project{} = project} <- get_project(project_id) |> ProjectCanEnableDonations.validate, + with {:ok, %Project{} = project} <- get_project(project_id), + {:ok, %Project{}} <- ProjectCanEnableDonations.validate(project), %{} = create_attributes <- get_create_attributes(project_id), connect_account_id <- project.organization.stripe_connect_account.id_from_stripe, {:ok, plan} <- @api.Plan.create(create_attributes, connect_account: connect_account_id), @@ -28,12 +32,10 @@ defmodule CodeCorps.StripeService.StripeConnectPlanService do |> StripeConnectPlan.create_changeset(params) |> Repo.insert else - nil -> {:error, :not_found} failure -> failure end end - @spec get_create_attributes(binary) :: map defp get_create_attributes(project_id) do %{ amount: 1, # in cents @@ -46,8 +48,11 @@ defmodule CodeCorps.StripeService.StripeConnectPlanService do end defp get_project(project_id) do - Project - |> Repo.get(project_id) - |> Repo.preload([:donation_goals, {:organization, :stripe_connect_account}, :stripe_connect_plan]) + preloads = [:donation_goals, {:organization, :stripe_connect_account}, :stripe_connect_plan] + + case Project |> Repo.get(project_id) |> Repo.preload(preloads) do + nil -> {:error, :not_found} + record -> {:ok, record} + end end end diff --git a/lib/code_corps/stripe_service/stripe_connect_subscription_service.ex b/lib/code_corps/stripe_service/stripe_connect_subscription_service.ex index 6be7bf63c..c560b8126 100644 --- a/lib/code_corps/stripe_service/stripe_connect_subscription_service.ex +++ b/lib/code_corps/stripe_service/stripe_connect_subscription_service.ex @@ -1,4 +1,9 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do + @moduledoc """ + Used to perform actions on `StripeConnectSubscription` records while propagating + to and from associated `Stripe.Subscription` records. + """ + alias CodeCorps.{ Project, Repo, StripeConnectCustomer, StripeConnectAccount, StripeConnectPlan, StripeConnectSubscription, User @@ -14,21 +19,18 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do Finds or creates a new `Stripe.Subscription` record on Stripe API, as well as an associated local `StripeConnectSubscription` record - # Possible return values - - - `{:ok, %StripeConnectSubscription{}}` - the created record. - - `{:error, %Ecto.Changeset{}}` - the record was not created due to validation issues. - - `{:error, :project_not_found}` - - `{:error, :project_not_ready}` - the associated project does not meed the prerequisites for receiving donations. - - `{:error, :user_not_found}` - - `{:error, :user_not_ready}` - the associated user does not meet the prerequisits to donate. - - `{:error, %Stripe.APIErrorResponse{}}` - there was a problem with the stripe request - # Side effects - If the subscription is created or found, associated project totals will get updated - If the subscription is created or found, associated donation goal states will be updated """ + @spec find_or_create(map) :: {:ok, StripeConnectSubscription.t} | + {:error, Ecto.Changeset.t} | + {:error, Stripe.APIErrorResponse.t} | + {:error, :project_not_found} | + {:error, :project_not_ready} | + {:error, :user_not_found} | + {:error, :user_not_ready} def find_or_create(%{"project_id" => project_id, "quantity" => _, "user_id" => user_id} = attributes) do with {:ok, %Project{} = project} <- get_project_with_preloads(project_id), {:ok, %Project{}} <- ProjectSubscribable.validate(project), @@ -47,15 +49,12 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do end @doc """ - Updates an existing `StripeConnectSubscription` record by retrieving a `Stripe.Subscription` record and - using that data as update parameters - - # Possible return values - - `{:ok, %StripeConnectSubscription{}}` - the updated record. - - `{:error, %Stripe.APIErrorResponse{}}` - there was a problem with the stripe request - - `{:error, :not_found}` - one of the associated records was not found - + Updates an existing `StripeConnectSubscription` record by retrieving + a `Stripe.Subscription` record and using that data as update parameters """ + @spec update_from_stripe(String.t, String.t) :: {:ok, StripeConnectSubscription.t} | + {:error, Stripe.APIErrorResponse.t} | + {:error, :not_found} def update_from_stripe(stripe_id, connect_customer_id) do with {:ok, %StripeConnectAccount{} = connect_account} <- retrieve_connect_account(connect_customer_id), {:ok, %Stripe.Subscription{} = stripe_subscription} <- @api.Subscription.retrieve(stripe_id, connect_account: connect_account.id), @@ -69,7 +68,6 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do {:ok, subscription} else - nil -> {:error, :not_found} failure -> failure end end @@ -111,7 +109,7 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do defp get_project_with_preloads(id) do preloads = [:stripe_connect_plan, [organization: :stripe_connect_account]] - case Repo.get(Project, id) |> Repo.preload(preloads) do + case Project |> Repo.get(id) |> Repo.preload(preloads) do nil -> {:error, :project_not_found} project -> {:ok, project} end @@ -120,7 +118,7 @@ defmodule CodeCorps.StripeService.StripeConnectSubscriptionService do defp get_user_with_preloads(user_id) do preloads = [:stripe_platform_customer, [{:stripe_platform_card, :stripe_connect_cards}]] - case Repo.get(User, user_id) |> Repo.preload(preloads) do + case User |> Repo.get(user_id) |> Repo.preload(preloads) do nil -> {:error, :user_not_found} user -> {:ok, user} end diff --git a/lib/code_corps/stripe_service/stripe_platform_card.ex b/lib/code_corps/stripe_service/stripe_platform_card.ex index 67837ae8a..c65a51c67 100644 --- a/lib/code_corps/stripe_service/stripe_platform_card.ex +++ b/lib/code_corps/stripe_service/stripe_platform_card.ex @@ -1,4 +1,8 @@ defmodule CodeCorps.StripeService.StripePlatformCardService do + @moduledoc """ + Used to perform actions on StripePlatformCard records, while propagating to + and from associated Stripe.Card records + """ alias CodeCorps.Repo alias CodeCorps.StripeService.Adapters.StripePlatformCardAdapter alias CodeCorps.StripeService.StripeConnectCardService @@ -8,6 +12,13 @@ defmodule CodeCorps.StripeService.StripePlatformCardService do @api Application.get_env(:code_corps, :stripe) + # Prevents warning for calling `Repo.transaction(multi)`. + # The warning was caused with how the function is internally + # implemented, so there's no way around it + # As we update Ecto, we should check if this is still necessary. + # Last check was Ecto 2.1.3 + @dialyzer :no_opaque + def create(%{"stripe_token" => stripe_token, "user_id" => user_id} = attributes) do with %StripePlatformCustomer{} = customer <- StripePlatformCustomer |> CodeCorps.Repo.get_by(user_id: user_id), {:ok, %Stripe.Card{} = card} <- @api.Card.create(:customer, customer.id_from_stripe, stripe_token), diff --git a/lib/code_corps/stripe_service/stripe_platform_customer.ex b/lib/code_corps/stripe_service/stripe_platform_customer.ex index 584cc141d..8834ddfc7 100644 --- a/lib/code_corps/stripe_service/stripe_platform_customer.ex +++ b/lib/code_corps/stripe_service/stripe_platform_customer.ex @@ -1,4 +1,8 @@ defmodule CodeCorps.StripeService.StripePlatformCustomerService do + @moduledoc """ + Used to perform actions on `StripePlatformCustomer` records while propagating + to and from associated `Stripe.Customer` records. + """ alias CodeCorps.Repo alias CodeCorps.{StripeConnectCustomer, StripePlatformCustomer, User} alias CodeCorps.StripeService.Adapters.StripePlatformCustomerAdapter @@ -8,6 +12,13 @@ defmodule CodeCorps.StripeService.StripePlatformCustomerService do @api Application.get_env(:code_corps, :stripe) + # Prevents warning for calling `Repo.transaction(multi)`. + # The warning was caused with how the function is internally + # implemented, so there's no way around it + # As we update Ecto, we should check if this is still necessary. + # Last check was Ecto 2.1.3 + @dialyzer :no_opaque + def create(attributes) do with stripe_attributes <- build_stripe_attributes(attributes), {:ok, customer} <- @api.Customer.create(stripe_attributes), diff --git a/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler.ex b/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler.ex index 8c8080b7e..f120afb63 100644 --- a/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler.ex +++ b/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler.ex @@ -30,13 +30,13 @@ defmodule CodeCorps.StripeService.WebhookProcessing.IgnoredEventHandler do Returns true or false depending on specified type """ - @spec should_handle?(String.t, Module.t) :: boolean + @spec should_handle?(String.t, module) :: boolean def should_handle?(type, handler), do: handler |> ignored_event_types |> Enum.member?(type) @doc """ Returns a list of event types which are being explicitly ignored by the application. """ - @spec ignored_event_types(Module.t) :: list + @spec ignored_event_types(module) :: list def ignored_event_types(ConnectEventHandler), do: @ignored_connect_event_types def ignored_event_types(PlatformEventHandler), do: @ignored_platform_event_types @@ -47,14 +47,14 @@ defmodule CodeCorps.StripeService.WebhookProcessing.IgnoredEventHandler do Returns `{:ok, %CodeCorps.StripeEvent{}} """ - @spec handle(StripeEvent.t, Module.t) :: {:ok, StripeEvent.t} + @spec handle(StripeEvent.t, module) :: {:ok, StripeEvent.t} def handle(%StripeEvent{type: type} = local_event, handler) do with ignored_reason <- get_reason(type, handler) do local_event |> set_ignored(ignored_reason) end end - @spec get_reason(String.t, Module.t) :: String.t + @spec get_reason(String.t, module) :: String.t defp get_reason(type, ConnectEventHandler), do: get_connect_reason(type) defp get_reason(type, PlatformEventHandler), do: get_platform_reason(type) diff --git a/mix.exs b/mix.exs index cd71cd609..520dad238 100644 --- a/mix.exs +++ b/mix.exs @@ -1,4 +1,6 @@ defmodule CodeCorps.Mixfile do + @moduledoc false + use Mix.Project def project do @@ -7,6 +9,7 @@ defmodule CodeCorps.Mixfile do elixir: "1.4.1", elixirc_paths: elixirc_paths(Mix.env), compilers: [:phoenix, :gettext] ++ Mix.compilers, + dialyzer: [plt_add_deps: :transitive], build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, aliases: aliases(), @@ -59,6 +62,7 @@ defmodule CodeCorps.Mixfile do [ {:bamboo, "~> 0.7"}, # emails {:bamboo_postmark, "~> 0.2.0"}, # postmark adapter for emails + {:dialyxir, "~> 0.4", only: [:dev, :test], runtime: false}, {:phoenix, "~> 1.2.1"}, {:phoenix_pubsub, "~> 1.0"}, {:phoenix_ecto, "~> 3.0"}, diff --git a/mix.lock b/mix.lock index 15239fbb1..0e0f79fc0 100644 --- a/mix.lock +++ b/mix.lock @@ -18,6 +18,7 @@ "credo": {:hex, :credo, "0.6.0", "44a82f82b94eeb4ba6092c89b8a6730ca1a3291c7940739d5acc8806d25ac991", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}]}, "db_connection": {:hex, :db_connection, "1.1.0", "b2b88db6d7d12f99997b584d09fad98e560b817a20dab6a526830e339f54cdb3", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]}, "decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], []}, + "dialyxir": {:hex, :dialyxir, "0.4.3", "a4daeebd0107de10d3bbae2ccb6b8905e69544db1ed5fe9148ad27cd4cb2c0cd", [:mix], []}, "earmark": {:hex, :earmark, "1.1.0", "8c2bf85d725050a92042bc1edf362621004d43ca6241c756f39612084e95487f", [:mix], []}, "ecto": {:hex, :ecto, "2.1.3", "ffb24e150b519a4c0e4c84f9eabc8587199389bc499195d5d1a93cd3b2d9a045", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]}, "ecto_ordered": {:hex, :ecto_ordered, "0.2.0-beta1", "cb066bc608f1c8913cea85af8293261720e6a88e3c99061e6877d7025352f045", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: false]}]}, diff --git a/priv/repo/structure.sql b/priv/repo/structure.sql index 4b763d9c1..cc2c1aa53 100644 --- a/priv/repo/structure.sql +++ b/priv/repo/structure.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 9.5.4 --- Dumped by pg_dump version 9.5.4 +-- Dumped from database version 9.5.1 +-- Dumped by pg_dump version 9.5.1 SET statement_timeout = 0; SET lock_timeout = 0; diff --git a/test/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler_test.exs b/test/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler_test.exs index 14052a823..cf1e85b88 100644 --- a/test/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler_test.exs +++ b/test/lib/code_corps/stripe_service/webhook_processing/ignored_event_handler_test.exs @@ -5,7 +5,7 @@ defmodule CodeCorps.StripeService.WebhookProcessing.IgnoredEventHandlerTest do ConnectEventHandler, IgnoredEventHandler, PlatformEventHandler } - @spec ignored?(String.t, Module.t) :: boolean + @spec ignored?(String.t, module) :: boolean defp ignored?(type, handler) do event = insert(:stripe_event, type: type) {:ok, event} = IgnoredEventHandler.handle(event, handler) diff --git a/test/models/donation_goal_test.exs b/test/models/donation_goal_test.exs index 4cddac497..07bb9e768 100644 --- a/test/models/donation_goal_test.exs +++ b/test/models/donation_goal_test.exs @@ -1,4 +1,6 @@ defmodule CodeCorps.DonationGoalTest do + @moduledoc false + use CodeCorps.ModelCase alias CodeCorps.DonationGoal @@ -16,7 +18,8 @@ defmodule CodeCorps.DonationGoalTest do test "ensures project with specified id actually exists" do attrs = %{amount: 100, description: "Bar", project_id: -1} {result, changeset} = - DonationGoal.create_changeset(%DonationGoal{}, attrs) + %DonationGoal{} + |> DonationGoal.create_changeset(attrs) |> Repo.insert assert result == :error @@ -47,5 +50,20 @@ defmodule CodeCorps.DonationGoalTest do refute changeset.valid? changeset |> assert_validation_triggered(:current, :required) end + + test "ensures only one donation goal per project can be current" do + project = insert(:project) + insert(:donation_goal, current: true, project: project) + donation_goal = insert(:donation_goal, project: project) + + attrs = %{current: true} + changeset = DonationGoal.set_current_changeset(donation_goal, attrs) + + assert changeset.valid? + + {:error, errored_changeset} = Repo.update(changeset) + + assert_error_message(errored_changeset, :current, "has already been taken") + end end end diff --git a/web/controllers/comment_controller.ex b/web/controllers/comment_controller.ex index cc38858b2..20db02da8 100644 --- a/web/controllers/comment_controller.ex +++ b/web/controllers/comment_controller.ex @@ -10,10 +10,12 @@ defmodule CodeCorps.CommentController do plug :load_and_authorize_resource, model: Comment, only: [:update] plug JaResource + @spec filter(Plug.Conn.t, Ecto.Query.t, String.t, String.t) :: Ecto.Query.t def filter(_conn, query, "id", id_list) do query |> id_filter(id_list) end + @spec handle_create(Plug.Conn.t, map) :: {:ok, Comment.t} | {:error, Ecto.Changeset.t} def handle_create(conn, attributes) do %Comment{} |> Comment.create_changeset(attributes) @@ -21,6 +23,7 @@ defmodule CodeCorps.CommentController do |> CodeCorps.Analytics.Segment.track(:created, conn) end + @spec handle_update(Plug.Conn.t, Comment.t, map) :: {:ok, Comment.t} | {:error, Ecto.Changeset.t} def handle_update(conn, comment, attributes) do comment |> Comment.changeset(attributes) diff --git a/web/controllers/task_skill_controller.ex b/web/controllers/task_skill_controller.ex index 1e834999c..4df990d63 100644 --- a/web/controllers/task_skill_controller.ex +++ b/web/controllers/task_skill_controller.ex @@ -11,7 +11,7 @@ defmodule CodeCorps.TaskSkillController do plug :load_and_authorize_resource, model: TaskSkill, only: [:delete] plug JaResource - @spec filter(Plug.Conn.t, Ecto.Query.t, String.t, list) :: Plug.Conn.t + @spec filter(Plug.Conn.t, Ecto.Query.t, String.t, String.t) :: Plug.Conn.t def filter(_conn, query, "id", id_list) do query |> id_filter(id_list) end diff --git a/web/controllers/user_skill_controller.ex b/web/controllers/user_skill_controller.ex index bef3ead31..0342700be 100644 --- a/web/controllers/user_skill_controller.ex +++ b/web/controllers/user_skill_controller.ex @@ -11,6 +11,7 @@ defmodule CodeCorps.UserSkillController do plug :load_and_authorize_resource, model: UserSkill, only: [:delete] plug JaResource + @spec filter(Plug.Conn.t, Ecto.Query.t, String.t, String.t) :: Ecto.Query.t def filter(_conn, query, "id", id_list) do query |> id_filter(id_list) end diff --git a/web/controllers/user_task_controller.ex b/web/controllers/user_task_controller.ex index 13b56a0f2..e0576c8b1 100644 --- a/web/controllers/user_task_controller.ex +++ b/web/controllers/user_task_controller.ex @@ -11,7 +11,7 @@ defmodule CodeCorps.UserTaskController do plug :load_and_authorize_resource, model: UserTask, only: [:delete] plug JaResource - @spec filter(Plug.Conn.t, Ecto.Query.t, String.t, list) :: Plug.Conn.t + @spec filter(Plug.Conn.t, Ecto.Query.t, String.t, String.t) :: Plug.Conn.t def filter(_conn, query, "id", id_list) do query |> id_filter(id_list) end diff --git a/web/gettext.ex b/web/gettext.ex index 08e221a51..ffc97ef4f 100644 --- a/web/gettext.ex +++ b/web/gettext.ex @@ -21,4 +21,10 @@ defmodule CodeCorps.Gettext do See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. """ use Gettext, otp_app: :code_corps + + @dialyzer [{:nowarn_function, 'MACRO-dgettext': 3}, + {:nowarn_function, 'MACRO-dgettext': 4}, + {:nowarn_function, 'MACRO-dngettext': 5}, + {:nowarn_function, 'MACRO-dngettext': 6}, + ] end diff --git a/web/models/category.ex b/web/models/category.ex index 3330dc682..a4b70a3c7 100644 --- a/web/models/category.ex +++ b/web/models/category.ex @@ -7,6 +7,8 @@ defmodule CodeCorps.Category do import CodeCorps.Helpers.Slug + @type t :: %__MODULE__{} + schema "categories" do field :name, :string field :slug, :string diff --git a/web/models/comment.ex b/web/models/comment.ex index 15494b2d7..d65ad9e7c 100644 --- a/web/models/comment.ex +++ b/web/models/comment.ex @@ -3,6 +3,8 @@ defmodule CodeCorps.Comment do alias CodeCorps.Services.MarkdownRendererService + @type t :: %__MODULE__{} + schema "comments" do field :body, :string field :markdown, :string diff --git a/web/models/donation_goal.ex b/web/models/donation_goal.ex index 42a2c70d8..572ef430f 100644 --- a/web/models/donation_goal.ex +++ b/web/models/donation_goal.ex @@ -10,6 +10,8 @@ defmodule CodeCorps.DonationGoal do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "donation_goals" do field :amount, :integer field :current, :boolean, default: false @@ -23,6 +25,7 @@ defmodule CodeCorps.DonationGoal do @doc """ Builds a changeset based on the `struct` and `params`. """ + @spec create_changeset(struct, map) :: Ecto.Changeset.t def create_changeset(struct, params \\ %{}) do struct |> cast(params, [:amount, :description, :project_id]) @@ -33,6 +36,7 @@ defmodule CodeCorps.DonationGoal do @doc """ Builds a changeset based on the `struct` and `params`. """ + @spec update_changeset(struct, map) :: Ecto.Changeset.t def update_changeset(struct, params \\ %{}) do struct |> cast(params, [:amount, :description]) @@ -42,10 +46,11 @@ defmodule CodeCorps.DonationGoal do @doc """ Builds a changeset based on the `struct` and `params`. """ + @spec set_current_changeset(struct, map) :: Ecto.Changeset.t def set_current_changeset(struct, params \\ %{}) do struct |> cast(params, [:current]) |> validate_required([:current]) - |> unique_constraint(:current, [:project_id]) + |> unique_constraint(:current, name: :donation_goals_current_unique_to_project) end end diff --git a/web/models/organization.ex b/web/models/organization.ex index 0d1a25e4d..43c495452 100644 --- a/web/models/organization.ex +++ b/web/models/organization.ex @@ -4,11 +4,15 @@ defmodule CodeCorps.Organization do """ use CodeCorps.Web, :model + import CodeCorps.Helpers.RandomIconColor import CodeCorps.Helpers.Slug import CodeCorps.Validators.SlugValidator + alias CodeCorps.SluggedRoute + @type t :: %__MODULE__{} + schema "organizations" do field :cloudinary_public_id field :default_color diff --git a/web/models/organization_membership.ex b/web/models/organization_membership.ex index a46cff2d3..c9f362f7f 100644 --- a/web/models/organization_membership.ex +++ b/web/models/organization_membership.ex @@ -5,6 +5,8 @@ defmodule CodeCorps.OrganizationMembership do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "organization_memberships" do field :role, :string diff --git a/web/models/preview.ex b/web/models/preview.ex index f350a6dca..c5744b30d 100644 --- a/web/models/preview.ex +++ b/web/models/preview.ex @@ -6,6 +6,8 @@ defmodule CodeCorps.Preview do use CodeCorps.Web, :model alias CodeCorps.Services.MarkdownRendererService + @type t :: %__MODULE__{} + schema "previews" do field :body, :string field :markdown, :string diff --git a/web/models/project.ex b/web/models/project.ex index adb21d942..a937862aa 100644 --- a/web/models/project.ex +++ b/web/models/project.ex @@ -4,12 +4,16 @@ defmodule CodeCorps.Project do """ use CodeCorps.Web, :model + import CodeCorps.Helpers.RandomIconColor import CodeCorps.Helpers.Slug import CodeCorps.Validators.SlugValidator + alias CodeCorps.Services.MarkdownRendererService alias CodeCorps.TaskList + @type t :: %__MODULE__{} + schema "projects" do field :approved, :boolean field :cloudinary_public_id diff --git a/web/models/project_category.ex b/web/models/project_category.ex index fdd6c7f74..5dafce1cc 100644 --- a/web/models/project_category.ex +++ b/web/models/project_category.ex @@ -5,6 +5,8 @@ defmodule CodeCorps.ProjectCategory do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "project_categories" do belongs_to :project, CodeCorps.Project belongs_to :category, CodeCorps.Category diff --git a/web/models/project_skill.ex b/web/models/project_skill.ex index 9f6c5fdc6..4004d966f 100644 --- a/web/models/project_skill.ex +++ b/web/models/project_skill.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.ProjectSkill do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "project_skills" do belongs_to :project, CodeCorps.Project belongs_to :skill, CodeCorps.Skill diff --git a/web/models/role.ex b/web/models/role.ex index 5ea0a60e8..6a1e81bc0 100644 --- a/web/models/role.ex +++ b/web/models/role.ex @@ -7,6 +7,8 @@ defmodule CodeCorps.Role do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "roles" do field :name, :string field :ability, :string @@ -21,11 +23,12 @@ defmodule CodeCorps.Role do @doc """ Builds a changeset based on the `struct` and `params`. """ + @spec changeset(CodeCorps.Role.t, map) :: Ecto.Changeset.t def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :ability, :kind]) |> validate_required([:name, :ability, :kind]) - |> validate_inclusion(:kind, kinds) + |> validate_inclusion(:kind, kinds()) end defp kinds do diff --git a/web/models/role_skill.ex b/web/models/role_skill.ex index d8711f048..f54fc272e 100644 --- a/web/models/role_skill.ex +++ b/web/models/role_skill.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.RoleSkill do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "role_skills" do field :cat, :integer @@ -13,6 +15,7 @@ defmodule CodeCorps.RoleSkill do @doc """ Builds a changeset based on the `struct` and `params`. """ + @spec changeset(CodeCorps.RoleSkill.t, map) :: Ecto.Changeset.t def changeset(struct, params \\ %{}) do struct |> cast(params, [:role_id, :skill_id]) @@ -25,11 +28,12 @@ defmodule CodeCorps.RoleSkill do @doc """ Builds a changeset for importing a category. """ + @spec import_changeset(CodeCorps.RoleSkill.t, map) :: Ecto.Changeset.t def import_changeset(struct, params) do struct |> changeset(params) |> cast(params, [:cat]) - |> validate_inclusion(:cat, cats) + |> validate_inclusion(:cat, cats()) end defp cats do diff --git a/web/models/skill.ex b/web/models/skill.ex index 571f4de4d..4142c716c 100644 --- a/web/models/skill.ex +++ b/web/models/skill.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.Skill do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "skills" do field :title, :string field :description, :string diff --git a/web/models/slugged_route.ex b/web/models/slugged_route.ex index 5466f9033..d25f21ae2 100644 --- a/web/models/slugged_route.ex +++ b/web/models/slugged_route.ex @@ -9,6 +9,8 @@ defmodule CodeCorps.SluggedRoute do import CodeCorps.Helpers.Slug import CodeCorps.Validators.SlugValidator + @type t :: %__MODULE__{} + schema "slugged_routes" do belongs_to :organization, CodeCorps.Organization belongs_to :user, CodeCorps.User diff --git a/web/models/stripe_connect_account.ex b/web/models/stripe_connect_account.ex index f824def9c..e5f29037d 100644 --- a/web/models/stripe_connect_account.ex +++ b/web/models/stripe_connect_account.ex @@ -5,6 +5,8 @@ defmodule CodeCorps.StripeConnectAccount do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_connect_accounts" do field :business_name, :string field :business_url, :string diff --git a/web/models/stripe_connect_card.ex b/web/models/stripe_connect_card.ex index 9936530c9..b2cdb45d1 100644 --- a/web/models/stripe_connect_card.ex +++ b/web/models/stripe_connect_card.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.StripeConnectCard do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_connect_cards" do field :id_from_stripe, :string, null: false diff --git a/web/models/stripe_connect_charge.ex b/web/models/stripe_connect_charge.ex index e59238bed..d1561c7f0 100644 --- a/web/models/stripe_connect_charge.ex +++ b/web/models/stripe_connect_charge.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.StripeConnectCharge do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_connect_charges" do field :amount, :integer field :amount_refunded, :integer diff --git a/web/models/stripe_connect_customer.ex b/web/models/stripe_connect_customer.ex index 7addfdab0..78c73d081 100644 --- a/web/models/stripe_connect_customer.ex +++ b/web/models/stripe_connect_customer.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.StripeConnectCustomer do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_connect_customers" do field :id_from_stripe, :string, null: false diff --git a/web/models/stripe_connect_plan.ex b/web/models/stripe_connect_plan.ex index a35f9fa8c..135bd92d8 100644 --- a/web/models/stripe_connect_plan.ex +++ b/web/models/stripe_connect_plan.ex @@ -11,6 +11,8 @@ defmodule CodeCorps.StripeConnectPlan do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_connect_plans" do field :amount, :integer field :created, :integer diff --git a/web/models/stripe_connect_subscription.ex b/web/models/stripe_connect_subscription.ex index 8b14c9e62..dcc7c9f6b 100644 --- a/web/models/stripe_connect_subscription.ex +++ b/web/models/stripe_connect_subscription.ex @@ -27,6 +27,8 @@ defmodule CodeCorps.StripeConnectSubscription do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_connect_subscriptions" do field :application_fee_percent, :decimal field :cancelled_at, :integer diff --git a/web/models/stripe_event.ex b/web/models/stripe_event.ex index 7357c5519..048ebda02 100644 --- a/web/models/stripe_event.ex +++ b/web/models/stripe_event.ex @@ -21,6 +21,8 @@ defmodule CodeCorps.StripeEvent do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_events" do field :endpoint, :string, null: false field :id_from_stripe, :string, null: false diff --git a/web/models/stripe_external_account.ex b/web/models/stripe_external_account.ex index 27ea785f2..2dacc965e 100644 --- a/web/models/stripe_external_account.ex +++ b/web/models/stripe_external_account.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.StripeExternalAccount do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_external_accounts" do field :id_from_stripe, :string, null: false field :account_id_from_stripe, :string, null: false diff --git a/web/models/stripe_file_upload.ex b/web/models/stripe_file_upload.ex index 73ae87ed7..17774c236 100644 --- a/web/models/stripe_file_upload.ex +++ b/web/models/stripe_file_upload.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.StripeFileUpload do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_file_uploads" do field :created, :integer field :id_from_stripe, :string, null: false diff --git a/web/models/stripe_invoice.ex b/web/models/stripe_invoice.ex index e2ef636d8..99c140b52 100644 --- a/web/models/stripe_invoice.ex +++ b/web/models/stripe_invoice.ex @@ -5,6 +5,8 @@ defmodule CodeCorps.StripeInvoice do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_invoices" do field :amount_due, :integer field :application_fee, :integer diff --git a/web/models/stripe_platform_card.ex b/web/models/stripe_platform_card.ex index a4f255bf9..fd0d32ea2 100644 --- a/web/models/stripe_platform_card.ex +++ b/web/models/stripe_platform_card.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.StripePlatformCard do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_platform_cards" do field :brand, :string field :customer_id_from_stripe, :string diff --git a/web/models/stripe_platform_customer.ex b/web/models/stripe_platform_customer.ex index 02ebd8914..a4001dd80 100644 --- a/web/models/stripe_platform_customer.ex +++ b/web/models/stripe_platform_customer.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.StripePlatformCustomer do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "stripe_platform_customers" do field :created, :integer field :currency, :string diff --git a/web/models/task.ex b/web/models/task.ex index 205a84741..688397344 100644 --- a/web/models/task.ex +++ b/web/models/task.ex @@ -1,8 +1,12 @@ defmodule CodeCorps.Task do use CodeCorps.Web, :model + import EctoOrdered + alias CodeCorps.Services.MarkdownRendererService + @type t :: %__MODULE__{} + schema "tasks" do field :body, :string field :markdown, :string diff --git a/web/models/task_list.ex b/web/models/task_list.ex index 3e81f04e5..720c6eeb6 100644 --- a/web/models/task_list.ex +++ b/web/models/task_list.ex @@ -4,6 +4,8 @@ defmodule CodeCorps.TaskList do alias CodeCorps.TaskList + @type t :: %__MODULE__{} + schema "task_lists" do field :inbox, :boolean, default: false field :name, :string diff --git a/web/models/user.ex b/web/models/user.ex index d36e09841..cf2c0d437 100644 --- a/web/models/user.ex +++ b/web/models/user.ex @@ -12,6 +12,8 @@ defmodule CodeCorps.User do alias Comeonin.Bcrypt alias Ecto.Changeset + @type t :: %__MODULE__{} + schema "users" do field :admin, :boolean field :biography, :string diff --git a/web/models/user_category.ex b/web/models/user_category.ex index a7ad5afff..132227997 100644 --- a/web/models/user_category.ex +++ b/web/models/user_category.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.UserCategory do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "user_categories" do belongs_to :user, CodeCorps.User belongs_to :category, CodeCorps.Category diff --git a/web/models/user_role.ex b/web/models/user_role.ex index f6a763e44..d54e226bd 100644 --- a/web/models/user_role.ex +++ b/web/models/user_role.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.UserRole do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "user_roles" do belongs_to :user, CodeCorps.User belongs_to :role, CodeCorps.Role diff --git a/web/models/user_skill.ex b/web/models/user_skill.ex index a486416b9..1f285301b 100644 --- a/web/models/user_skill.ex +++ b/web/models/user_skill.ex @@ -1,6 +1,8 @@ defmodule CodeCorps.UserSkill do use CodeCorps.Web, :model + @type t :: %__MODULE__{} + schema "user_skills" do belongs_to :user, CodeCorps.User belongs_to :skill, CodeCorps.Skill diff --git a/web/policies/organization_membership_policy.ex b/web/policies/organization_membership_policy.ex index 49b717164..59ffc4823 100644 --- a/web/policies/organization_membership_policy.ex +++ b/web/policies/organization_membership_policy.ex @@ -1,4 +1,7 @@ defmodule CodeCorps.OrganizationMembershipPolicy do + @moduledoc """ + Handles `User` authorization of actions on `OrganizationMembership` records + """ import CodeCorps.Helpers.Policy, only: [get_membership: 2, get_role: 1] alias CodeCorps.Organization @@ -7,10 +10,12 @@ defmodule CodeCorps.OrganizationMembershipPolicy do alias CodeCorps.User alias Ecto.Changeset + @spec create?(User.t, Ecto.Changeset.t) :: boolean def create?(%User{admin: true}, %Changeset{}), do: true def create?(%User{id: user_id}, %Changeset{changes: %{member_id: member_id}}), do: user_id == member_id def create?(%User{}, %Changeset{}), do: false + @spec update?(User.t, Ecto.Changeset.t) :: boolean def update?(%User{admin: true}, %Changeset{}), do: true def update?(%User{} = user, %Changeset{data: %OrganizationMembership{} = current_membership} = changeset) do user_membership = current_membership |> get_user_membership(user) @@ -35,6 +40,7 @@ defmodule CodeCorps.OrganizationMembershipPolicy do end end + @spec delete?(User.t, OrganizationMembership.t) :: boolean def delete?(%User{admin: true}, %OrganizationMembership{}), do: true def delete?(%User{} = user, %OrganizationMembership{} = current_membership) do current_membership |> get_user_membership(user) |> do_delete?(current_membership) diff --git a/web/views/page_view.ex b/web/views/page_view.ex index 55ee4f7cb..47172a091 100644 --- a/web/views/page_view.ex +++ b/web/views/page_view.ex @@ -1,3 +1,4 @@ defmodule CodeCorps.PageView do use CodeCorps.Web, :view + @dialyzer :no_match end