Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions lib/plausible/site.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ defmodule Plausible.Site do
end

def new(params) do
changeset(%__MODULE__{}, params)
create_changeset(%__MODULE__{}, params)
|> put_change(:onboarding_status, initial_onboarding_status(params))
end

Expand All @@ -108,39 +108,37 @@ defmodule Plausible.Site do
@changeset_cast_fields [:domain, :timezone, :legacy_time_on_page_cutoff]
end

def changeset(site, attrs \\ %{}) do
def create_changeset(site, attrs \\ %{}) do
site
|> cast(attrs, @changeset_cast_fields)
|> clean_domain()
|> validate_required([:domain], message: "Please enter a domain or subdomain")
|> validate_domain()
|> validate_required([:timezone])
|> validate_timezone()
|> validate_domain_format()
|> validate_domain_reserved_characters()
|> unique_constraint(:domain,
message: @domain_unique_error
)
|> unique_constraint(:domain,
name: "domain_change_disallowed",
message: @domain_unique_error
)
|> put_change(
:ingest_rate_limit_threshold,
Application.get_env(:plausible, __MODULE__)[:default_ingest_threshold]
)
end

def update_changeset(site, attrs \\ %{}, opts \\ []) do
def domain_changeset(site, attrs \\ %{}, opts \\ []) do
at =
opts
|> Keyword.get(:at, NaiveDateTime.utc_now())
|> NaiveDateTime.truncate(:second)

site
|> changeset(attrs)
|> cast(attrs, [:domain])
|> validate_domain()
|> handle_domain_change(at)
end

def tz_changeset(site, attrs \\ %{}) do
site
|> cast(attrs, [:timezone])
|> validate_required([:timezone])
|> validate_timezone()
end

def crm_changeset(site, attrs) do
site
|> cast(attrs, [
Expand Down Expand Up @@ -218,6 +216,21 @@ defmodule Plausible.Site do
end
end

defp validate_domain(changeset) do
changeset
|> clean_domain()
|> validate_required([:domain], message: "Please enter a domain or subdomain")
|> validate_domain_format()
|> validate_domain_reserved_characters()
|> unique_constraint(:domain,
message: @domain_unique_error
)
|> unique_constraint(:domain,
name: "domain_change_disallowed",
message: @domain_unique_error
)
end

defp clean_domain(changeset) do
clean_domain =
(get_field(changeset, :domain) || "")
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible/site/domain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ defmodule Plausible.Site.Domain do
@spec change(Site.t(), String.t(), Keyword.t()) ::
{:ok, Site.t()} | {:error, Ecto.Changeset.t()}
def change(%Site{} = site, new_domain, opts \\ []) do
changeset = Site.update_changeset(site, %{domain: new_domain}, opts)
changeset = Site.domain_changeset(site, %{domain: new_domain}, opts)

changeset =
if is_nil(changeset.errors[:domain]) and is_nil(changeset.changes[:domain]) do
Expand Down
6 changes: 3 additions & 3 deletions lib/plausible_web/controllers/site_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ defmodule PlausibleWeb.SiteController do

defp render_new_site_form(conn, flow, opts \\ []) do
defaults = [
changeset: Plausible.Site.changeset(%Plausible.Site{}),
changeset: Plausible.Site.create_changeset(%Plausible.Site{}),
site_limit_exceeded?: false,
form_submit_url: "/sites?flow=#{flow}",
bg_class: "bg-white dark:bg-gray-950",
Expand Down Expand Up @@ -107,7 +107,7 @@ defmodule PlausibleWeb.SiteController do
conn
|> render("settings_general.html",
site: site,
changeset: Plausible.Site.changeset(site, %{}),
changeset: Plausible.Site.tz_changeset(site),
connect_live_socket: true,
dogfood_page_path: "/:dashboard/settings/general",
layout: {PlausibleWeb.LayoutView, "site_settings.html"}
Expand Down Expand Up @@ -279,7 +279,7 @@ defmodule PlausibleWeb.SiteController do

def update_settings(conn, %{"site" => site_params}) do
site = conn.assigns[:site]
changeset = Plausible.Site.update_changeset(site, site_params)
changeset = Plausible.Site.tz_changeset(site, site_params)

case Repo.update(changeset) do
{:ok, site} ->
Expand Down
4 changes: 2 additions & 2 deletions lib/plausible_web/live/change_domain/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule PlausibleWeb.Live.ChangeDomain.Form do
import Ecto.Query

def update(assigns, socket) do
changeset = Site.update_changeset(assigns.site)
changeset = Site.domain_changeset(assigns.site)

{:ok,
socket
Expand Down Expand Up @@ -50,7 +50,7 @@ defmodule PlausibleWeb.Live.ChangeDomain.Form do

def handle_event("submit", %{"site" => %{"domain" => new_domain}}, socket) do
site = socket.assigns.site
preview_changeset = Site.update_changeset(site, %{domain: new_domain})
preview_changeset = Site.domain_changeset(site, %{domain: new_domain})
cleaned_domain = Ecto.Changeset.get_field(preview_changeset, :domain)

if same_team_owns_domain?(site, cleaned_domain) do
Expand Down
20 changes: 18 additions & 2 deletions test/plausible/site/domain_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,26 @@ defmodule Plausible.Site.DomainTest do

test "new domain gets validated" do
site = build(:site)
changeset = Site.update_changeset(site, %{domain: " "})
changeset = Site.domain_changeset(site, %{domain: " "})
assert {"Please enter a domain or subdomain", _} = changeset.errors[:domain]

changeset = Site.update_changeset(site, %{domain: "?#[]"})
changeset = Site.domain_changeset(site, %{domain: "?#[]"})
assert {"must not contain URI reserved characters" <> _, _} = changeset.errors[:domain]
end

test "only the domain is changed" do
site = build(:site, ingest_rate_limit_threshold: 100)

changeset =
Site.domain_changeset(site, %{
domain: "new.example.com",
timezone: "Europe/London",
consolidated: true,
legacy_time_on_page_cutoff: ~D[2020-01-01],
ingest_rate_limit_threshold: 5
})

assert Map.keys(changeset.changes) |> Enum.sort() ==
[:domain, :domain_changed_at, :domain_changed_from]
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3822,7 +3822,7 @@ defmodule PlausibleWeb.Api.ExternalStatsController.QueryTest do

describe "using the returned query object in a new POST request" do
test "yields the same results for a simple aggregate query", %{conn: conn, site: site} do
Plausible.Site.changeset(site, %{timezone: "Europe/Tallinn"})
Plausible.Site.tz_changeset(site, %{timezone: "Europe/Tallinn"})
|> Plausible.Repo.update()

populate_stats(site, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
conn: conn,
site: site
} do
Plausible.Site.changeset(site, %{timezone: "Europe/Tallinn"})
Plausible.Site.tz_changeset(site, %{timezone: "Europe/Tallinn"})
|> Plausible.Repo.update()

populate_stats(site, [
Expand Down Expand Up @@ -1331,7 +1331,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
} do
# On Oct 26, 2025, in Tallinn, Estonia, the clocks shifted back
# from 04:00 -> 03:00, so there were 25 hours in that day.
Plausible.Site.changeset(site, %{timezone: "Europe/Tallinn"})
Plausible.Site.tz_changeset(site, %{timezone: "Europe/Tallinn"})
|> Plausible.Repo.update!()

populate_stats(site, [
Expand Down Expand Up @@ -1458,7 +1458,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
} do
site =
site
|> Plausible.Site.changeset(%{timezone: "Europe/Copenhagen"})
|> Plausible.Site.tz_changeset(%{timezone: "Europe/Copenhagen"})
|> Plausible.Repo.update!()

populate_stats(site, [
Expand Down
25 changes: 25 additions & 0 deletions test/plausible_web/controllers/site_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,31 @@ defmodule PlausibleWeb.SiteControllerTest do
assert updated.timezone == "Europe/London"
assert redirected_to(conn, 302) == "/#{URI.encode_www_form(site.domain)}/settings/general"
end

test "ignores fields other than the timezone", %{conn: conn, site: site} do
put(conn, "/#{site.domain}/settings", %{
"site" => %{
"timezone" => "Europe/London",
"domain" => "renamed.example.com",
"consolidated" => "true",
"legacy_time_on_page_cutoff" => "2020-01-01"
}
})

updated = Repo.get(Plausible.Site, site.id)
assert updated.timezone == "Europe/London"
assert updated.domain == site.domain
refute updated.domain_changed_from
refute updated.consolidated
assert updated.legacy_time_on_page_cutoff == site.legacy_time_on_page_cutoff
end

test "rejects an invalid timezone", %{conn: conn, site: site} do
conn = put(conn, "/#{site.domain}/settings", %{"site" => %{"timezone" => "Not/AZone"}})

assert html_response(conn, 200) =~ "Could not update your site settings"
assert Repo.get(Plausible.Site, site.id).timezone == site.timezone
end
end

describe "POST /sites/:domain/make-public" do
Expand Down
2 changes: 1 addition & 1 deletion test/workers/set_legacy_time_on_page_cutoff_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ defmodule Plausible.Workers.SetLegacyTimeOnPageCutoffTest do

# Work around model defaults
site
|> Plausible.Site.changeset(%{legacy_time_on_page_cutoff: cutoff})
|> Ecto.Changeset.change(legacy_time_on_page_cutoff: cutoff)
|> Plausible.Repo.update()

site
Expand Down
Loading