From 1e7c6e19a25b49c11f3d09c424948e8c3aad7b01 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Wed, 8 Mar 2017 14:10:58 -0800 Subject: [PATCH] Fixes #739 and fixes #740 by adding website to projects --- lib/code_corps/helpers/url.ex | 25 +++++++++++++ .../20170308214128_add_website_to_project.exs | 9 +++++ ..._add_should_link_externally_to_project.exs | 9 +++++ priv/repo/structure.sql | 10 +++--- test/lib/code_corps/helpers/url_test.exs | 31 ++++++++++++++++ test/models/project_test.exs | 36 +++++++++++++++++++ test/support/factories.ex | 3 +- test/views/project_view_test.exs | 2 ++ web/models/project.ex | 6 ++++ web/models/user.ex | 12 ++----- web/views/project_view.ex | 4 ++- 11 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 lib/code_corps/helpers/url.ex create mode 100644 priv/repo/migrations/20170308214128_add_website_to_project.exs create mode 100644 priv/repo/migrations/20170308220713_add_should_link_externally_to_project.exs create mode 100644 test/lib/code_corps/helpers/url_test.exs diff --git a/lib/code_corps/helpers/url.ex b/lib/code_corps/helpers/url.ex new file mode 100644 index 000000000..307c0e262 --- /dev/null +++ b/lib/code_corps/helpers/url.ex @@ -0,0 +1,25 @@ +defmodule CodeCorps.Helpers.URL do + @moduledoc """ + Provides some helpers for assembling and validating URLs. + """ + + alias Ecto.Changeset + + @doc """ + Prefixes the URL with `http://` in the event that `http://` and `https://` are + not already the starting format. If `nil`, simply returns `nil`. + """ + def prefix_url(changeset, key) do + changeset + |> Changeset.update_change(key, &do_prefix_url/1) + end + + defp do_prefix_url(nil), do: nil + defp do_prefix_url("http://" <> rest), do: "http://" <> rest + defp do_prefix_url("https://" <> rest), do: "https://" <> rest + defp do_prefix_url(value), do: "http://" <> value + + def valid_format do + ~r/\A((http|https):\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(([0-9]{1,5})?\/.*)?#=\z/ix + end +end diff --git a/priv/repo/migrations/20170308214128_add_website_to_project.exs b/priv/repo/migrations/20170308214128_add_website_to_project.exs new file mode 100644 index 000000000..3743da7df --- /dev/null +++ b/priv/repo/migrations/20170308214128_add_website_to_project.exs @@ -0,0 +1,9 @@ +defmodule CodeCorps.Repo.Migrations.AddWebsiteToProject do + use Ecto.Migration + + def change do + alter table(:projects) do + add :website, :string + end + end +end diff --git a/priv/repo/migrations/20170308220713_add_should_link_externally_to_project.exs b/priv/repo/migrations/20170308220713_add_should_link_externally_to_project.exs new file mode 100644 index 000000000..301b05b78 --- /dev/null +++ b/priv/repo/migrations/20170308220713_add_should_link_externally_to_project.exs @@ -0,0 +1,9 @@ +defmodule CodeCorps.Repo.Migrations.AddShouldLinkExternallyToProject do + use Ecto.Migration + + def change do + alter table(:projects) do + add :should_link_externally, :boolean, default: false + end + end +end diff --git a/priv/repo/structure.sql b/priv/repo/structure.sql index 58e3fc6a2..befc38c48 100644 --- a/priv/repo/structure.sql +++ b/priv/repo/structure.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 9.5.1 --- Dumped by pg_dump version 9.5.1 +-- Dumped from database version 9.5.4 +-- Dumped by pg_dump version 9.5.4 SET statement_timeout = 0; SET lock_timeout = 0; @@ -369,7 +369,9 @@ CREATE TABLE projects ( approved boolean DEFAULT false, cloudinary_public_id character varying(255), default_color character varying(255), - owner_id integer + owner_id integer, + website character varying(255), + should_link_externally boolean DEFAULT false ); @@ -2608,5 +2610,5 @@ ALTER TABLE ONLY user_tasks -- PostgreSQL database dump complete -- -INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201014901), (20170201025454), (20170201035458), (20170201183258), (20170220032224), (20170224233516), (20170226050552), (20170228085250), (20170228144515), (20170228145755); +INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201014901), (20170201025454), (20170201035458), (20170201183258), (20170220032224), (20170224233516), (20170226050552), (20170228085250), (20170308214128), (20170308220713); diff --git a/test/lib/code_corps/helpers/url_test.exs b/test/lib/code_corps/helpers/url_test.exs new file mode 100644 index 000000000..bf647724e --- /dev/null +++ b/test/lib/code_corps/helpers/url_test.exs @@ -0,0 +1,31 @@ +defmodule CodeCorps.Helpers.URLTest do + use ExUnit.Case, async: true + import CodeCorps.Helpers.URL + alias Ecto.Changeset + + test "returns nil when nil" do + changeset = create_prefixed_changeset(nil) + assert Changeset.get_change(changeset, :url) == nil + end + + test "returns the original when starts with http://" do + original = "http://www.google.com" + changeset = create_prefixed_changeset(original) + assert Changeset.get_change(changeset, :url) == original + end + + test "returns the original when starts with https://" do + original = "https://www.google.com" + changeset = create_prefixed_changeset(original) + assert Changeset.get_change(changeset, :url) == original + end + + test "returns prefixed with http:// in every other case" do + changeset = create_prefixed_changeset("www.google.com") + assert Changeset.get_change(changeset, :url) == "http://www.google.com" + end + + defp create_prefixed_changeset(value) do + %Changeset{changes: %{url: value}} |> prefix_url(:url) + end +end diff --git a/test/models/project_test.exs b/test/models/project_test.exs index 15500926b..a42594e1b 100644 --- a/test/models/project_test.exs +++ b/test/models/project_test.exs @@ -96,5 +96,41 @@ defmodule CodeCorps.ProjectTest do changeset = Project.update_changeset(%Project{}, %{organization_id: 1}) assert :error == changeset |> fetch_change(:organization_id) end + + test "requires :website to be in proper format" do + project = %Project{} + attrs = %{website: "bad <> website"} + + changeset = Project.update_changeset(project, attrs) + + assert_error_message(changeset, :website, "has invalid format") + end + + test "doesn't require :website to be part of the changes" do + project = %Project{} + attrs = %{} + + changeset = Project.update_changeset(project, attrs) + + refute Keyword.has_key?(changeset.errors, :website) + end + + test "prefixes website with 'http://' if there is no prefix" do + project = %Project{website: "https://first.com"} + attrs = %{website: "example.com"} + + changeset = Project.update_changeset(project, attrs) + + assert changeset.changes.website == "http://example.com" + end + + test "doesn't make a change to the url when there is no param for it" do + project = %Project{website: "https://first.com"} + attrs = %{} + + changeset = Project.update_changeset(project, attrs) + + refute Map.has_key?(changeset.changes, :website) + end end end diff --git a/test/support/factories.ex b/test/support/factories.ex index 3ff3b2175..9ccaa5241 100644 --- a/test/support/factories.ex +++ b/test/support/factories.ex @@ -78,7 +78,8 @@ defmodule CodeCorps.Factories do title: sequence(:title, &"Project #{&1}"), slug: sequence(:slug, &"project-#{&1}"), organization: build(:organization), - owner: build(:user) + owner: build(:user), + website: sequence(:website, &"http://test-#{&1}.com") } end diff --git a/test/views/project_view_test.exs b/test/views/project_view_test.exs index e04ecb9eb..b74ba22de 100644 --- a/test/views/project_view_test.exs +++ b/test/views/project_view_test.exs @@ -30,10 +30,12 @@ defmodule CodeCorps.ProjectViewTest do "inserted-at" => project.inserted_at, "long-description-body" => project.long_description_body, "long-description-markdown" => project.long_description_markdown, + "should-link-externally" => project.should_link_externally, "slug" => project.slug, "title" => project.title, "total-monthly-donated" => project.total_monthly_donated, "updated-at" => project.updated_at, + "website" => project.website, }, "id" => project.id |> Integer.to_string, "relationships" => %{ diff --git a/web/models/project.ex b/web/models/project.ex index 00a4d9c8c..f7941111b 100644 --- a/web/models/project.ex +++ b/web/models/project.ex @@ -7,6 +7,7 @@ defmodule CodeCorps.Project do import CodeCorps.Helpers.RandomIconColor import CodeCorps.Helpers.Slug + import CodeCorps.Helpers.URL, only: [prefix_url: 2] import CodeCorps.Validators.SlugValidator alias CodeCorps.Services.MarkdownRendererService @@ -21,9 +22,11 @@ defmodule CodeCorps.Project do field :description, :string field :long_description_body, :string field :long_description_markdown, :string + field :should_link_externally, :boolean, default: false # temporary for linking to projects off Code Corps while in alpha field :slug, :string field :title, :string field :total_monthly_donated, :integer, default: 0 + field :website, :string belongs_to :organization, CodeCorps.Organization belongs_to :owner, CodeCorps.User @@ -73,6 +76,9 @@ defmodule CodeCorps.Project do def update_changeset(struct, params) do struct |> changeset(params) + |> cast(params, [:website]) + |> prefix_url(:website) + |> validate_format(:website, CodeCorps.Helpers.URL.valid_format()) end def update_total_changeset(struct, params) do diff --git a/web/models/user.ex b/web/models/user.ex index c3e45e64e..bf396c467 100644 --- a/web/models/user.ex +++ b/web/models/user.ex @@ -6,6 +6,7 @@ defmodule CodeCorps.User do use CodeCorps.Web, :model import CodeCorps.Helpers.RandomIconColor + import CodeCorps.Helpers.URL, only: [prefix_url: 2] import CodeCorps.Validators.SlugValidator alias CodeCorps.SluggedRoute @@ -90,7 +91,7 @@ defmodule CodeCorps.User do |> changeset(params) |> cast(params, [:biography, :cloudinary_public_id, :first_name, :last_name, :state_transition, :twitter, :website]) |> prefix_url(:website) - |> validate_format(:website, ~r/\A((http|https):\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(([0-9]{1,5})?\/.*)?#=\z/ix) + |> validate_format(:website, CodeCorps.Helpers.URL.valid_format()) |> validate_format(:twitter, ~r/\A[a-zA-Z0-9_]{1,15}\z/) |> apply_state_transition(struct) end @@ -136,15 +137,6 @@ defmodule CodeCorps.User do end end - defp prefix_url(changeset, key) do - changeset - |> update_change(key, &do_prefix_url/1) - end - defp do_prefix_url(nil), do: nil - defp do_prefix_url("http://" <> rest), do: "http://" <> rest - defp do_prefix_url("https://" <> rest), do: "https://" <> rest - defp do_prefix_url(value), do: "http://" <> value - defp check_email_valid(struct, email) do struct |> Map.put(:valid, String.match?(email, ~r/@/)) diff --git a/web/views/project_view.ex b/web/views/project_view.ex index ee7ec219a..14f3ae7be 100644 --- a/web/views/project_view.ex +++ b/web/views/project_view.ex @@ -15,7 +15,9 @@ defmodule CodeCorps.ProjectView do :slug, :title, :can_activate_donations, :cloudinary_public_id, :description, :donations_active, :icon_thumb_url, :icon_large_url, :long_description_body, :long_description_markdown, - :inserted_at, :total_monthly_donated, :updated_at] + :inserted_at, :should_link_externally, :total_monthly_donated, :updated_at, + :website + ] has_one :organization, serializer: CodeCorps.OrganizationView has_one :owner, serializer: CodeCorps.UserView