diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3e97d60eb4def66342019304578f2d3e1107b0a6
--- /dev/null
+++ b/.github/workflows/elixir.yml
@@ -0,0 +1,61 @@
+name: Elixir CI
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  build:
+    name: Build and test
+    runs-on: ubuntu-latest
+
+    services:
+      postgres:
+        image: postgres:12
+        ports:
+          - 5432:5432
+        env:
+          POSTGRES_PASSWORD: postgres
+          options: >-
+            --health-cmd pg_isready
+            --health-interval 10s
+            --health-timeout 5s
+            --health-retries 5
+
+      clickhouse:
+        image: yandex/clickhouse-server:20
+        ports:
+          - 8123:8123
+        env:
+          options: >-
+            --health-cmd nc -zw3 localhost 8124
+            --health-interval 10s
+            --health-timeout 5s
+            --health-retries 5
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Read .tool-versions
+      uses: marocchino/tool-versions-action@v1
+      id: versions
+    - name: Set up Elixir
+      uses: actions/setup-elixir@v1
+      with:
+        elixir-version: ${{steps.versions.outputs.elixir}}
+        otp-version: ${{ steps.versions.outputs.erlang}}
+    - name: Restore dependencies cache
+      uses: actions/cache@v2
+      with:
+        path: deps
+        key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
+        restore-keys: ${{ runner.os }}-mix-
+    - name: Install dependencies
+      run: mix deps.get
+    - name: Check Formatting
+      run: mix format --check-formatted
+    - name: Run tests
+      run: mix test
+env:
+  MIX_ENV: test
diff --git a/lib/plausible/auth/auth.ex b/lib/plausible/auth/auth.ex
index c08d3e6e5663c22c183b94e50c06cfff8e8406c5..6475cbda4ea148fec420802278b1f175835cbf1e 100644
--- a/lib/plausible/auth/auth.ex
+++ b/lib/plausible/auth/auth.ex
@@ -4,11 +4,18 @@ defmodule Plausible.Auth do
   alias Plausible.Stats.Clickhouse, as: Stats
 
   def issue_email_verification(user) do
-    Repo.update_all(from(c in "email_verification_codes", where: c.user_id == ^user.id), [set: [user_id: nil]])
+    Repo.update_all(from(c in "email_verification_codes", where: c.user_id == ^user.id),
+      set: [user_id: nil]
+    )
 
-    code = Repo.one(from(c in "email_verification_codes", where: is_nil(c.user_id), select: c.code, limit: 1))
+    code =
+      Repo.one(
+        from(c in "email_verification_codes", where: is_nil(c.user_id), select: c.code, limit: 1)
+      )
 
-    Repo.update_all(from(c in "email_verification_codes", where: c.code == ^code), [set: [user_id: user.id, issued_at: Timex.now()]])
+    Repo.update_all(from(c in "email_verification_codes", where: c.code == ^code),
+      set: [user_id: user.id, issued_at: Timex.now()]
+    )
 
     code
   end
@@ -18,21 +25,34 @@ defmodule Plausible.Auth do
   end
 
   def verify_email(user, code) do
-    found_code = Repo.one(
-      from c in "email_verification_codes",
-      where: c.user_id == ^user.id,
-      where: c.code == ^code,
-      select: %{code: c.code, issued: c.issued_at}
-    )
+    found_code =
+      Repo.one(
+        from c in "email_verification_codes",
+          where: c.user_id == ^user.id,
+          where: c.code == ^code,
+          select: %{code: c.code, issued: c.issued_at}
+      )
 
     cond do
-      is_nil(found_code) -> {:error, :incorrect}
-      is_expired?(found_code[:issued]) -> {:error, :expired}
+      is_nil(found_code) ->
+        {:error, :incorrect}
+
+      is_expired?(found_code[:issued]) ->
+        {:error, :expired}
+
       true ->
-        {:ok, _} = Ecto.Multi.new
-        |> Ecto.Multi.update(:user, Plausible.Auth.User.changeset(user, %{email_verified: true}))
-        |> Ecto.Multi.update_all(:codes, from(c in "email_verification_codes", where: c.user_id == ^user.id), [set: [user_id: nil]])
-        |> Repo.transaction
+        {:ok, _} =
+          Ecto.Multi.new()
+          |> Ecto.Multi.update(
+            :user,
+            Plausible.Auth.User.changeset(user, %{email_verified: true})
+          )
+          |> Ecto.Multi.update_all(
+            :codes,
+            from(c in "email_verification_codes", where: c.user_id == ^user.id),
+            set: [user_id: nil]
+          )
+          |> Repo.transaction()
 
         :ok
     end
diff --git a/lib/plausible/auth/user.ex b/lib/plausible/auth/user.ex
index eef1714b72f176faa9e436a243549d5ae9bed4e3..67848868db7fe26ea9cc3b78a8e5e881209d37fe 100644
--- a/lib/plausible/auth/user.ex
+++ b/lib/plausible/auth/user.ex
@@ -60,5 +60,6 @@ defmodule Plausible.Auth.User do
     hash = Plausible.Auth.Password.hash(changes[:password])
     change(changeset, password_hash: hash)
   end
+
   def hash_password(changeset), do: changeset
 end
diff --git a/lib/plausible/session/write_buffer.ex b/lib/plausible/session/write_buffer.ex
index 7d371374013b398992c971be80375d78250874b7..3a85a8e74fce61e44a01493093d0852ca1df843d 100644
--- a/lib/plausible/session/write_buffer.ex
+++ b/lib/plausible/session/write_buffer.ex
@@ -51,9 +51,11 @@ defmodule Plausible.Session.WriteBuffer do
 
       sessions ->
         Logger.info("Flushing #{length(sessions)} sessions")
-        sessions = sessions
+
+        sessions =
+          sessions
           |> Enum.map(&(Map.from_struct(&1) |> Map.delete(:__meta__)))
-          |> Enum.reverse
+          |> Enum.reverse()
 
         Plausible.ClickhouseRepo.insert_all(Plausible.ClickhouseSession, sessions)
     end
diff --git a/lib/plausible/site/schema.ex b/lib/plausible/site/schema.ex
index 985296dce572bd3d2c54529a3f01f5136ecadd36..edb99938ca74cdf1d35c9be199f289fc94191163 100644
--- a/lib/plausible/site/schema.ex
+++ b/lib/plausible/site/schema.ex
@@ -23,7 +23,9 @@ defmodule Plausible.Site do
     site
     |> cast(attrs, [:domain, :timezone])
     |> validate_required([:domain, :timezone])
-    |> validate_format(:domain, ~r/^[a-zA-z0-9\-\.\/\:]*$/, message: "only letters, numbers, slashes and period allowed")
+    |> validate_format(:domain, ~r/^[a-zA-z0-9\-\.\/\:]*$/,
+      message: "only letters, numbers, slashes and period allowed"
+    )
     |> unique_constraint(:domain)
     |> clean_domain
   end
diff --git a/lib/plausible/stats/clickhouse.ex b/lib/plausible/stats/clickhouse.ex
index 5570cd95fa689f3d3af2cf41917c08cb9dd668ab..d1acdcf7a2956bb1068a97157cbd00d637b95f59 100644
--- a/lib/plausible/stats/clickhouse.ex
+++ b/lib/plausible/stats/clickhouse.ex
@@ -167,6 +167,7 @@ defmodule Plausible.Stats.Clickhouse do
 
   def unique_visitors(site, query) do
     query = if query.period == "realtime", do: %Query{query | period: "30m"}, else: query
+
     ClickhouseRepo.one(
       from e in base_query_w_sessions(site, query),
         select: fragment("uniq(user_id)")
@@ -764,6 +765,7 @@ defmodule Plausible.Stats.Clickhouse do
     |> Enum.filter(fn row -> row[:count] > 0 end)
     |> Enum.map(fn row ->
       uri = URI.parse(row[:name])
+
       if uri.host && uri.scheme do
         Map.put(row, :is_url, true)
       else
@@ -773,15 +775,16 @@ defmodule Plausible.Stats.Clickhouse do
   end
 
   def last_24h_visitors([]), do: %{}
+
   def last_24h_visitors(sites) do
     domains = Enum.map(sites, & &1.domain)
 
     ClickhouseRepo.all(
       from e in "events",
-      group_by: e.domain,
-      where: fragment("? IN tuple(?)", e.domain, ^domains),
-      where: e.timestamp > fragment("now() - INTERVAL 24 HOUR"),
-      select: {e.domain, fragment("uniq(user_id)")}
+        group_by: e.domain,
+        where: fragment("? IN tuple(?)", e.domain, ^domains),
+        where: e.timestamp > fragment("now() - INTERVAL 24 HOUR"),
+        select: {e.domain, fragment("uniq(user_id)")}
     )
     |> Enum.into(%{})
   end
@@ -968,8 +971,8 @@ defmodule Plausible.Stats.Clickhouse do
     q =
       if query.filters["source"] || query.filters['referrer'] || query.filters["utm_medium"] ||
            query.filters["utm_source"] || query.filters["utm_campaign"] || query.filters["screen"] ||
-             query.filters["browser"] || query.filters["browser_version"] || query.filters["os"] ||
-               query.filters["os_version"] || query.filters["country"] do
+           query.filters["browser"] || query.filters["browser_version"] || query.filters["os"] ||
+           query.filters["os_version"] || query.filters["country"] do
         from(
           e in q,
           join: sq in subquery(sessions_q),
diff --git a/lib/plausible/stats/query.ex b/lib/plausible/stats/query.ex
index 35eddd8c2c3f17a6816df58eeda1da49c162980e..8f2e9db61a6e3e192f8aa67a4d8d84c3ed09f77b 100644
--- a/lib/plausible/stats/query.ex
+++ b/lib/plausible/stats/query.ex
@@ -7,13 +7,24 @@ defmodule Plausible.Stats.Query do
   end
 
   def shift_back(%__MODULE__{period: "month"} = query, site) do
-    {new_first, new_last} = if Timex.compare(Timex.now(site.timezone), query.date_range.first, :month) == 0  do # Querying current month to date
-      diff = Timex.diff(Timex.beginning_of_month(Timex.now(site.timezone)), Timex.now(site.timezone), :days) - 1
-      {query.date_range.first |> Timex.shift(days: diff), Timex.now(site.timezone) |> Timex.to_date |> Timex.shift(days: diff)}
-    else
-      diff = Timex.diff(query.date_range.first, query.date_range.last, :days) - 1
-      {query.date_range.first |> Timex.shift(days: diff), query.date_range.last |> Timex.shift(days: diff)}
-    end
+    # Querying current month to date
+    {new_first, new_last} =
+      if Timex.compare(Timex.now(site.timezone), query.date_range.first, :month) == 0 do
+        diff =
+          Timex.diff(
+            Timex.beginning_of_month(Timex.now(site.timezone)),
+            Timex.now(site.timezone),
+            :days
+          ) - 1
+
+        {query.date_range.first |> Timex.shift(days: diff),
+         Timex.now(site.timezone) |> Timex.to_date() |> Timex.shift(days: diff)}
+      else
+        diff = Timex.diff(query.date_range.first, query.date_range.last, :days) - 1
+
+        {query.date_range.first |> Timex.shift(days: diff),
+         query.date_range.last |> Timex.shift(days: diff)}
+      end
 
     Map.put(query, :date_range, Date.range(new_first, new_last))
   end
diff --git a/lib/plausible_web/controllers/api/external_controller.ex b/lib/plausible_web/controllers/api/external_controller.ex
index ea37b90104226c1c990d47e70cb02cf1e70df93e..6f910dc9f1610738615efe5c472544d8a5a998c6 100644
--- a/lib/plausible_web/controllers/api/external_controller.ex
+++ b/lib/plausible_web/controllers/api/external_controller.ex
@@ -119,9 +119,11 @@ defmodule PlausibleWeb.Api.ExternalController do
     end
   end
 
-
   defp is_bot?(%UAInspector.Result.Bot{}), do: true
-  defp is_bot?(%UAInspector.Result{client: %UAInspector.Result.Client{name: "Headless Chrome"}}), do: true
+
+  defp is_bot?(%UAInspector.Result{client: %UAInspector.Result.Client{name: "Headless Chrome"}}),
+    do: true
+
   defp is_bot?(_), do: false
 
   defp parse_meta(params) do
@@ -137,8 +139,9 @@ defmodule PlausibleWeb.Api.ExternalController do
   defp get_pathname(nil, _), do: "/"
 
   defp get_pathname(uri, hash_mode) do
-    pathname = (uri.path || "/")
-               |> URI.decode
+    pathname =
+      (uri.path || "/")
+      |> URI.decode()
 
     if hash_mode && uri.fragment do
       pathname <> "#" <> URI.decode(uri.fragment)
@@ -158,9 +161,8 @@ defmodule PlausibleWeb.Api.ExternalController do
     end
   end
 
-
-
   defp parse_referrer(_, nil), do: nil
+
   defp parse_referrer(uri, referrer_str) do
     referrer_uri = URI.parse(referrer_str)
 
@@ -222,6 +224,7 @@ defmodule PlausibleWeb.Api.ExternalController do
   end
 
   defp major_minor(:unknown), do: ""
+
   defp major_minor(version) do
     version
     |> String.split(".")
diff --git a/lib/plausible_web/controllers/api/stats_controller.ex b/lib/plausible_web/controllers/api/stats_controller.ex
index 97f724571005a05d3571735944eb00fffb33dd9a..643858c62aefb8405004f6e69f49f0cc7ed0799f 100644
--- a/lib/plausible_web/controllers/api/stats_controller.ex
+++ b/lib/plausible_web/controllers/api/stats_controller.ex
@@ -84,7 +84,6 @@ defmodule PlausibleWeb.Api.StatsController do
     prev_bounce_rate = Stats.bounce_rate(site, prev_query)
     change_bounce_rate = if prev_bounce_rate > 0, do: bounce_rate - prev_bounce_rate
 
-
     visit_duration =
       if !query.filters["page"] do
         duration = Stats.visit_duration(site, query)
diff --git a/lib/plausible_web/controllers/auth_controller.ex b/lib/plausible_web/controllers/auth_controller.ex
index 5cb5bb0fb85757d6f7210b60e77bb5162831e82a..2194049e5c1a9858be7c926c9acb2317befbfbb4 100644
--- a/lib/plausible_web/controllers/auth_controller.ex
+++ b/lib/plausible_web/controllers/auth_controller.ex
@@ -8,7 +8,14 @@ defmodule PlausibleWeb.AuthController do
        when action in [:register_form, :register, :login_form, :login]
 
   plug PlausibleWeb.RequireAccountPlug
-       when action in [:user_settings, :save_settings, :delete_me, :password_form, :set_password, :activate_form]
+       when action in [
+              :user_settings,
+              :save_settings,
+              :delete_me,
+              :password_form,
+              :set_password,
+              :activate_form
+            ]
 
   def register_form(conn, _params) do
     if Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_registration) do
@@ -37,7 +44,10 @@ defmodule PlausibleWeb.AuthController do
 
           conn
           |> put_session(:current_user_id, user.id)
-          |> put_resp_cookie("logged_in", "true", [http_only: false, max_age: 60 * 60 * 24 * 365 * 5000])
+          |> put_resp_cookie("logged_in", "true",
+            http_only: false,
+            max_age: 60 * 60 * 24 * 365 * 5000
+          )
           |> redirect(to: "/activate")
 
         {:error, changeset} ->
@@ -58,12 +68,16 @@ defmodule PlausibleWeb.AuthController do
   def activate_form(conn, _params) do
     user = conn.assigns[:current_user]
 
-    has_code = Repo.exists?(
-      from c in "email_verification_codes",
-      where: c.user_id == ^user.id
-    )
+    has_code =
+      Repo.exists?(
+        from c in "email_verification_codes",
+          where: c.user_id == ^user.id
+      )
 
-    render(conn, "activate.html", has_pin: has_code, layout: {PlausibleWeb.LayoutView, "focus.html"})
+    render(conn, "activate.html",
+      has_pin: has_code,
+      layout: {PlausibleWeb.LayoutView, "focus.html"}
+    )
   end
 
   def activate(conn, %{"code" => code}) do
@@ -73,12 +87,14 @@ defmodule PlausibleWeb.AuthController do
     case Auth.verify_email(user, code) do
       :ok ->
         redirect(conn, to: "/sites/new")
+
       {:error, :incorrect} ->
         render(conn, "activate.html",
           error: "Incorrect activation code",
           has_pin: true,
           layout: {PlausibleWeb.LayoutView, "focus.html"}
         )
+
       {:error, :expired} ->
         render(conn, "activate.html",
           error: "Code is expired, please request another one",
@@ -220,7 +236,10 @@ defmodule PlausibleWeb.AuthController do
 
         conn
         |> put_session(:current_user_id, user.id)
-        |> put_resp_cookie("logged_in", "true", [http_only: false, max_age: 60 * 60 * 24 * 365 * 5000])
+        |> put_resp_cookie("logged_in", "true",
+          http_only: false,
+          max_age: 60 * 60 * 24 * 365 * 5000
+        )
         |> put_session(:login_dest, nil)
         |> redirect(to: login_dest)
       else
diff --git a/lib/plausible_web/controllers/site_controller.ex b/lib/plausible_web/controllers/site_controller.ex
index 2676340fefd4d16757d3cc87f3194ed404ded152..01f38bdd9ea320cd30826516dcd95e163d71fdd9 100644
--- a/lib/plausible_web/controllers/site_controller.ex
+++ b/lib/plausible_web/controllers/site_controller.ex
@@ -7,12 +7,15 @@ defmodule PlausibleWeb.SiteController do
 
   def index(conn, _params) do
     user = conn.assigns[:current_user]
-    sites = Repo.all(
-      from s in Plausible.Site,
-      join: sm in Plausible.Site.Membership, on: sm.site_id == s.id,
-      where: sm.user_id == ^user.id,
-      order_by: s.domain
-    )
+
+    sites =
+      Repo.all(
+        from s in Plausible.Site,
+          join: sm in Plausible.Site.Membership,
+          on: sm.site_id == s.id,
+          where: sm.user_id == ^user.id,
+          order_by: s.domain
+      )
 
     visitors = Plausible.Stats.Clickhouse.last_24h_visitors(sites)
     render(conn, "index.html", sites: sites, visitors: visitors)
@@ -22,12 +25,17 @@ defmodule PlausibleWeb.SiteController do
     current_user = conn.assigns[:current_user]
     changeset = Plausible.Site.changeset(%Plausible.Site{})
 
-    is_first_site = !Repo.exists?(
-      from sm in Plausible.Site.Membership,
-      where: sm.user_id == ^current_user.id
-    )
+    is_first_site =
+      !Repo.exists?(
+        from sm in Plausible.Site.Membership,
+          where: sm.user_id == ^current_user.id
+      )
 
-    render(conn, "new.html", changeset: changeset, is_first_site: is_first_site, layout: {PlausibleWeb.LayoutView, "focus.html"})
+    render(conn, "new.html",
+      changeset: changeset,
+      is_first_site: is_first_site,
+      layout: {PlausibleWeb.LayoutView, "focus.html"}
+    )
   end
 
   def create_site(conn, %{"site" => site_params}) do
@@ -37,11 +45,13 @@ defmodule PlausibleWeb.SiteController do
       {:ok, %{site: site}} ->
         Plausible.Slack.notify("#{user.name} created #{site.domain} [email=#{user.email}]")
 
-        is_first_site = !Repo.exists?(
-          from sm in Plausible.Site.Membership,
-          where: sm.user_id == ^user.id
-          and sm.site_id != ^site.id
-        )
+        is_first_site =
+          !Repo.exists?(
+            from sm in Plausible.Site.Membership,
+              where:
+                sm.user_id == ^user.id and
+                  sm.site_id != ^site.id
+          )
 
         if is_first_site do
           PlausibleWeb.Email.welcome_email(user)
@@ -53,10 +63,11 @@ defmodule PlausibleWeb.SiteController do
         |> redirect(to: "/#{URI.encode_www_form(site.domain)}/snippet")
 
       {:error, :site, changeset, _} ->
-        is_first_site = !Repo.exists?(
-          from sm in Plausible.Site.Membership,
-          where: sm.user_id == ^user.id
-        )
+        is_first_site =
+          !Repo.exists?(
+            from sm in Plausible.Site.Membership,
+              where: sm.user_id == ^user.id
+          )
 
         render(conn, "new.html",
           changeset: changeset,
@@ -68,19 +79,26 @@ defmodule PlausibleWeb.SiteController do
 
   def add_snippet(conn, %{"website" => website}) do
     user = conn.assigns[:current_user]
+
     site =
       Sites.get_for_user!(conn.assigns[:current_user].id, website)
       |> Repo.preload(:custom_domain)
 
-    is_first_site = !Repo.exists?(
-      from sm in Plausible.Site.Membership,
-      where: sm.user_id == ^user.id
-      and sm.site_id != ^site.id
-    )
+    is_first_site =
+      !Repo.exists?(
+        from sm in Plausible.Site.Membership,
+          where:
+            sm.user_id == ^user.id and
+              sm.site_id != ^site.id
+      )
 
     conn
     |> assign(:skip_plausible_tracking, true)
-    |> render("snippet.html", site: site, is_first_site: is_first_site, layout: {PlausibleWeb.LayoutView, "focus.html"})
+    |> render("snippet.html",
+      site: site,
+      is_first_site: is_first_site,
+      layout: {PlausibleWeb.LayoutView, "focus.html"}
+    )
   end
 
   def new_goal(conn, %{"website" => website}) do
@@ -129,8 +147,9 @@ defmodule PlausibleWeb.SiteController do
   end
 
   def settings_general(conn, %{"website" => website}) do
-    site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
-           |> Repo.preload(:custom_domain)
+    site =
+      Sites.get_for_user!(conn.assigns[:current_user].id, website)
+      |> Repo.preload(:custom_domain)
 
     conn
     |> assign(:skip_plausible_tracking, true)
@@ -168,7 +187,8 @@ defmodule PlausibleWeb.SiteController do
   end
 
   def settings_search_console(conn, %{"website" => website}) do
-    site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
+    site =
+      Sites.get_for_user!(conn.assigns[:current_user].id, website)
       |> Repo.preload(:google_auth)
 
     search_console_domains =
@@ -200,12 +220,16 @@ defmodule PlausibleWeb.SiteController do
   end
 
   def settings_custom_domain(conn, %{"website" => website}) do
-    site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
+    site =
+      Sites.get_for_user!(conn.assigns[:current_user].id, website)
       |> Repo.preload(:custom_domain)
 
     conn
     |> assign(:skip_plausible_tracking, true)
-    |> render("settings_custom_domain.html", site: site, layout: {PlausibleWeb.LayoutView, "site_settings.html"})
+    |> render("settings_custom_domain.html",
+      site: site,
+      layout: {PlausibleWeb.LayoutView, "site_settings.html"}
+    )
   end
 
   def settings_danger_zone(conn, %{"website" => website}) do
diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex
index f89c6b3f931aa93a1e0864dc721aab5c3cb17f43..4557d3ae742ee158cc044c25d641dd0392807521 100644
--- a/lib/plausible_web/email.ex
+++ b/lib/plausible_web/email.ex
@@ -13,6 +13,7 @@ defmodule PlausibleWeb.Email do
     |> subject("#{code} is your Plausible email verification code")
     |> render("activation_email.html", user: user, code: code)
   end
+
   def welcome_email(user) do
     base_email()
     |> to(user)
@@ -98,7 +99,12 @@ defmodule PlausibleWeb.Email do
     |> to(email)
     |> tag("spike-notification")
     |> subject("Traffic spike on #{site.domain}")
-    |> render("spike_notification.html", %{site: site, current_visitors: current_visitors, sources: sources, link: dashboard_link})
+    |> render("spike_notification.html", %{
+      site: site,
+      current_visitors: current_visitors,
+      sources: sources,
+      link: dashboard_link
+    })
   end
 
   def cancellation_email(user) do
diff --git a/lib/plausible_web/plugs/require_logged_out.ex b/lib/plausible_web/plugs/require_logged_out.ex
index ee6c174541b9991bd4569fd2ffc9ef834d5a1602..ac35a4e7fb28e19e7e545a97533f6c0e31072bb7 100644
--- a/lib/plausible_web/plugs/require_logged_out.ex
+++ b/lib/plausible_web/plugs/require_logged_out.ex
@@ -9,7 +9,10 @@ defmodule PlausibleWeb.RequireLoggedOutPlug do
     cond do
       conn.assigns[:current_user] ->
         conn
-        |> put_resp_cookie("logged_in", "true", [http_only: false, max_age: 60 * 60 * 24 * 365 * 5000])
+        |> put_resp_cookie("logged_in", "true",
+          http_only: false,
+          max_age: 60 * 60 * 24 * 365 * 5000
+        )
         |> Phoenix.Controller.redirect(to: "/sites")
         |> halt
 
diff --git a/lib/plausible_web/plugs/tracker.ex b/lib/plausible_web/plugs/tracker.ex
index 553225ab776e3821c2a35863c88e8c60e62df322..b55a99d3825106d958a6c5445a269d44f72e0a0b 100644
--- a/lib/plausible_web/plugs/tracker.ex
+++ b/lib/plausible_web/plugs/tracker.ex
@@ -20,8 +20,7 @@ defmodule PlausibleWeb.Tracker do
   def init(_) do
     templates =
       Enum.reduce(@templates, %{}, fn template_filename, rendered_templates ->
-        rendered =
-          EEx.compile_file("priv/tracker/js/" <> template_filename)
+        rendered = EEx.compile_file("priv/tracker/js/" <> template_filename)
 
         aliases = Map.get(@aliases, template_filename, [])
 
@@ -36,7 +35,9 @@ defmodule PlausibleWeb.Tracker do
 
   def call(conn, templates: templates) do
     case templates[conn.request_path] do
-      nil -> conn
+      nil ->
+        conn
+
       found ->
         {js, _} = Code.eval_quoted(found, base_url: PlausibleWeb.Endpoint.url())
         send_js(conn, js)
diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex
index 2e0014046782559435f41434a1964cc7ea4dc4c7..57ef97cf7188bdad27d8c6700fc8fadeb8d029a3 100644
--- a/lib/plausible_web/router.ex
+++ b/lib/plausible_web/router.ex
@@ -135,6 +135,7 @@ defmodule PlausibleWeb.Router do
     post "/sites/:website/spike-notification/enable", SiteController, :enable_spike_notification
     post "/sites/:website/spike-notification/disable", SiteController, :disable_spike_notification
     put "/sites/:website/spike-notification", SiteController, :update_spike_notification
+
     post "/sites/:website/spike-notification/recipients",
          SiteController,
          :add_spike_notification_recipient
diff --git a/lib/plausible_web/views/layout_view.ex b/lib/plausible_web/views/layout_view.ex
index d8a776bbacd81bc6a8aa6129636796419b022e9f..68053c0c64d4d01c4385c08d4eafd26518920a93 100644
--- a/lib/plausible_web/views/layout_view.ex
+++ b/lib/plausible_web/views/layout_view.ex
@@ -29,11 +29,10 @@ defmodule PlausibleWeb.LayoutView do
       [key: "Search Console", value: "search-console"],
       [key: "Email reports", value: "email-reports"],
       [key: "Custom domain", value: "custom-domain"],
-      [key: "Danger zone", value: "danger-zone"],
+      [key: "Danger zone", value: "danger-zone"]
     ]
   end
 
-
   def trial_notificaton(user) do
     case Plausible.Billing.trial_days_left(user) do
       days when days > 1 ->
diff --git a/lib/workers/clean_email_verification_codes.ex b/lib/workers/clean_email_verification_codes.ex
index 72069d3625d18ee28040d7b3d0b25dbbcfaba859..5b7af9272bfe49c8934a47f106fefd0a8c3af760 100644
--- a/lib/workers/clean_email_verification_codes.ex
+++ b/lib/workers/clean_email_verification_codes.ex
@@ -9,7 +9,7 @@ defmodule Plausible.Workers.CleanEmailVerificationCodes do
         where: not is_nil(c.user_id),
         where: c.issued_at < fragment("now() - INTERVAL '4 hours'")
       ),
-      [set: [user_id: nil]]
+      set: [user_id: nil]
     )
   end
 end
diff --git a/lib/workers/spike_notifier.ex b/lib/workers/spike_notifier.ex
index add1af17f8cd0e0ff42400230078d70bcb53fa6c..5d2a0fe6dd81c9b073b93a37e8f2fb34f84d495b 100644
--- a/lib/workers/spike_notifier.ex
+++ b/lib/workers/spike_notifier.ex
@@ -7,12 +7,13 @@ defmodule Plausible.Workers.SpikeNotifier do
 
   @impl Oban.Worker
   def perform(_args, _job, clickhouse \\ Plausible.Stats.Clickhouse) do
-    notifications = Repo.all(
-      from sn in SpikeNotification,
-      where: is_nil(sn.last_sent),
-      or_where: sn.last_sent < fragment("now() - INTERVAL ?", @at_most_every),
-      preload: :site
-    )
+    notifications =
+      Repo.all(
+        from sn in SpikeNotification,
+          where: is_nil(sn.last_sent),
+          or_where: sn.last_sent < fragment("now() - INTERVAL ?", @at_most_every),
+          preload: :site
+      )
 
     for notification <- notifications do
       query = Query.from(notification.site.timezone, %{"period" => "realtime"})
@@ -27,20 +28,30 @@ defmodule Plausible.Workers.SpikeNotifier do
       for recipient <- notification.recipients do
         send_notification(recipient, notification.site, current_visitors, sources)
       end
+
       notification
       |> SpikeNotification.was_sent()
-      |> Repo.update
+      |> Repo.update()
     end
   end
 
   defp send_notification(recipient, site, current_visitors, sources) do
     site = Repo.preload(site, :members)
 
-    dashboard_link = if Enum.member?(site.members, recipient) do
-      PlausibleWeb.Endpoint.url() <> "/" <> URI.encode_www_form(site.domain)
-    end
+    dashboard_link =
+      if Enum.member?(site.members, recipient) do
+        PlausibleWeb.Endpoint.url() <> "/" <> URI.encode_www_form(site.domain)
+      end
+
+    template =
+      PlausibleWeb.Email.spike_notification(
+        recipient,
+        site,
+        current_visitors,
+        sources,
+        dashboard_link
+      )
 
-    template = PlausibleWeb.Email.spike_notification(recipient, site, current_visitors, sources, dashboard_link)
     try do
       Plausible.Mailer.send_email(template)
     rescue
diff --git a/priv/repo/migrations/20201210085345_add_email_verified_to_users.exs b/priv/repo/migrations/20201210085345_add_email_verified_to_users.exs
index 822409c1e2f3095dca57fff76d413182299f16db..d170f1952d3a2d824634a8ee3ca7c40f81ab2848 100644
--- a/priv/repo/migrations/20201210085345_add_email_verified_to_users.exs
+++ b/priv/repo/migrations/20201210085345_add_email_verified_to_users.exs
@@ -9,6 +9,6 @@ defmodule Plausible.Repo.Migrations.AddEmailVerifiedToUsers do
 
     flush()
 
-    Repo.update_all("users", [set: [email_verified: true]])
+    Repo.update_all("users", set: [email_verified: true])
   end
 end
diff --git a/priv/repo/migrations/20201214072008_add_theme_pref_to_users.exs b/priv/repo/migrations/20201214072008_add_theme_pref_to_users.exs
index 48e4eb13517d92cd4b1896c1d4e3e444fdc1af84..3bd588008b3572e465ec8915723287ab11f29b2c 100644
--- a/priv/repo/migrations/20201214072008_add_theme_pref_to_users.exs
+++ b/priv/repo/migrations/20201214072008_add_theme_pref_to_users.exs
@@ -3,7 +3,7 @@ defmodule Plausible.Repo.Migrations.AddThemePrefToUsers do
 
   def change do
     alter table(:users) do
-      add_if_not_exists :theme, :string, default: "system"
+      add_if_not_exists(:theme, :string, default: "system")
     end
   end
 end
diff --git a/test/plausible_web/controllers/api/external_controller_test.exs b/test/plausible_web/controllers/api/external_controller_test.exs
index ebc3c83305907c22d972223098e4c614f04dcffe..25a8a58d357a8a6e2af6ca44a42e28bc11f6f9cb 100644
--- a/test/plausible_web/controllers/api/external_controller_test.exs
+++ b/test/plausible_web/controllers/api/external_controller_test.exs
@@ -110,7 +110,10 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
 
       conn
       |> put_req_header("content-type", "text/plain")
-      |> put_req_header("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/85.0.4183.83 Safari/537.36")
+      |> put_req_header(
+        "user-agent",
+        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/85.0.4183.83 Safari/537.36"
+      )
       |> post("/api/event", Jason.encode!(params))
 
       assert get_event("headless-chrome-test.com") == nil
@@ -396,10 +399,11 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
       name: "Signup",
       url: "http://gigride.live/",
       domain: "custom-prop-test.com",
-      props: Jason.encode!(%{
-        bool_test: true,
-        number_test: 12
-      })
+      props:
+        Jason.encode!(%{
+          bool_test: true,
+          number_test: 12
+        })
     }
 
     conn
@@ -508,7 +512,8 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
   test "decodes URL pathname, fragment and search", %{conn: conn} do
     params = %{
       n: "pageview",
-      u: "https://test.com/%EF%BA%9D%EF%BB%AD%EF%BA%8E%EF%BA%8B%EF%BA%AF-%EF%BB%AE%EF%BB%A4%EF%BA%B3%EF%BA%8E%EF%BA%92%EF%BB%97%EF%BA%8E%EF%BA%97?utm_source=%25balle%25",
+      u:
+        "https://test.com/%EF%BA%9D%EF%BB%AD%EF%BA%8E%EF%BA%8B%EF%BA%AF-%EF%BB%AE%EF%BB%A4%EF%BA%B3%EF%BA%8E%EF%BA%92%EF%BB%97%EF%BA%8E%EF%BA%97?utm_source=%25balle%25",
       d: "url-decode-test.com",
       h: 1
     }
diff --git a/test/plausible_web/controllers/api/stats_controller/browsers_test.exs b/test/plausible_web/controllers/api/stats_controller/browsers_test.exs
index e00252d4a17467688fc1848c9217837787370418..9b9ced4955490573551c1006781c964f33e9661a 100644
--- a/test/plausible_web/controllers/api/stats_controller/browsers_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/browsers_test.exs
@@ -20,7 +20,14 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
 
     test "returns top browser versions by unique visitors", %{conn: conn, site: site} do
       filters = Jason.encode!(%{browser: "Chrome"})
-      conn = get(conn, "/api/stats/#{site.domain}/browser-versions?period=day&date=2019-01-01&filters=#{filters}")
+
+      conn =
+        get(
+          conn,
+          "/api/stats/#{site.domain}/browser-versions?period=day&date=2019-01-01&filters=#{
+            filters
+          }"
+        )
 
       assert json_response(conn, 200) == [
                %{"name" => "78.0", "count" => 1, "percentage" => 100}
diff --git a/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs b/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs
index 71658d93f28cdb33def7f068152494fec2693e27..5329e4afa0cf6d7d25eda776ee8a0fc729071d2d 100644
--- a/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs
@@ -20,7 +20,14 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
 
     test "returns top OS versions by unique visitors", %{conn: conn, site: site} do
       filters = Jason.encode!(%{os: "Mac"})
-      conn = get(conn, "/api/stats/#{site.domain}/operating-system-versions?period=day&date=2019-01-01&filters=#{filters}")
+
+      conn =
+        get(
+          conn,
+          "/api/stats/#{site.domain}/operating-system-versions?period=day&date=2019-01-01&filters=#{
+            filters
+          }"
+        )
 
       assert json_response(conn, 200) == [
                %{"name" => "10.15", "count" => 1, "percentage" => 100}
diff --git a/test/plausible_web/controllers/auth_controller_test.exs b/test/plausible_web/controllers/auth_controller_test.exs
index 81391b256c64dcef186c02a97d304a09883ee00e..6a927f4fe0986c4dd457da77e3223a5add56b520 100644
--- a/test/plausible_web/controllers/auth_controller_test.exs
+++ b/test/plausible_web/controllers/auth_controller_test.exs
@@ -49,7 +49,7 @@ defmodule PlausibleWeb.AuthControllerTest do
             name: "Jane Doe",
             email: "user@example.com",
             password: "very-secret",
-            password_confirmation: "very-secret",
+            password_confirmation: "very-secret"
           }
         )
 
@@ -63,7 +63,7 @@ defmodule PlausibleWeb.AuthControllerTest do
             name: "Jane Doe",
             email: "user@example.com",
             password: "very-secret",
-            password_confirmation: "very-secret",
+            password_confirmation: "very-secret"
           }
         )
 
@@ -80,9 +80,11 @@ defmodule PlausibleWeb.AuthControllerTest do
       assert html_response(conn, 200) =~ "Request activation code"
     end
 
-    test "if user does have a code: prompts user to enter the activation code from their email", %{conn: conn} do
-      conn = post(conn, "/activate/request-code")
-             |> get("/activate")
+    test "if user does have a code: prompts user to enter the activation code from their email",
+         %{conn: conn} do
+      conn =
+        post(conn, "/activate/request-code")
+        |> get("/activate")
 
       assert html_response(conn, 200) =~ "Please enter the 4-digit code we sent to"
     end
@@ -94,7 +96,12 @@ defmodule PlausibleWeb.AuthControllerTest do
     test "associates an activation pin with the user account", %{conn: conn, user: user} do
       post(conn, "/activate/request-code")
 
-      code = Repo.one(from c in "email_verification_codes", where: c.user_id == ^user.id, select: %{user_id: c.user_id, issued_at: c.issued_at})
+      code =
+        Repo.one(
+          from c in "email_verification_codes",
+            where: c.user_id == ^user.id,
+            select: %{user_id: c.user_id, issued_at: c.issued_at}
+        )
 
       assert code[:user_id] == user.id
       assert Timex.after?(code[:issued_at], Timex.now() |> Timex.shift(seconds: -10))
@@ -119,11 +126,13 @@ defmodule PlausibleWeb.AuthControllerTest do
     end
 
     test "with expired pin - reloads the form with error", %{conn: conn, user: user} do
-      Repo.insert_all("email_verification_codes", [%{
-        code: 1234,
-        user_id: user.id,
-        issued_at: Timex.shift(Timex.now(), days: -1)
-      }])
+      Repo.insert_all("email_verification_codes", [
+        %{
+          code: 1234,
+          user_id: user.id,
+          issued_at: Timex.shift(Timex.now(), days: -1)
+        }
+      ])
 
       conn = post(conn, "/activate", %{code: "1234"})
 
@@ -134,7 +143,11 @@ defmodule PlausibleWeb.AuthControllerTest do
       Repo.update!(Plausible.Auth.User.changeset(user, %{email_verified: false}))
       post(conn, "/activate/request-code")
 
-      code = Repo.one(from c in "email_verification_codes", where: c.user_id == ^user.id, select: c.code) |> Integer.to_string
+      code =
+        Repo.one(
+          from c in "email_verification_codes", where: c.user_id == ^user.id, select: c.code
+        )
+        |> Integer.to_string()
 
       conn = post(conn, "/activate", %{code: code})
       user = Repo.get_by(Plausible.Auth.User, id: user.id)
@@ -147,7 +160,11 @@ defmodule PlausibleWeb.AuthControllerTest do
       Repo.update!(Plausible.Auth.User.changeset(user, %{email_verified: false}))
       post(conn, "/activate/request-code")
 
-      code = Repo.one(from c in "email_verification_codes", where: c.user_id == ^user.id, select: c.code) |> Integer.to_string
+      code =
+        Repo.one(
+          from c in "email_verification_codes", where: c.user_id == ^user.id, select: c.code
+        )
+        |> Integer.to_string()
 
       post(conn, "/activate", %{code: code})
 
diff --git a/test/plausible_web/controllers/site_controller_test.exs b/test/plausible_web/controllers/site_controller_test.exs
index 8b1e7c1f4401e479fc0f1d19f634d823f60a3b1c..751dcfad582c397983ae46e20868a7bde0d29a3b 100644
--- a/test/plausible_web/controllers/site_controller_test.exs
+++ b/test/plausible_web/controllers/site_controller_test.exs
@@ -57,7 +57,10 @@ defmodule PlausibleWeb.SiteControllerTest do
       assert_email_delivered_with(subject: "Welcome to Plausible")
     end
 
-    test "does not send welcome email if user already has a previous site", %{conn: conn, user: user} do
+    test "does not send welcome email if user already has a previous site", %{
+      conn: conn,
+      user: user
+    } do
       insert(:site, members: [user])
 
       post(conn, "/sites", %{
@@ -395,7 +398,11 @@ defmodule PlausibleWeb.SiteControllerTest do
   describe "POST /sites/:website/spike-notification/enable" do
     setup [:create_user, :log_in, :create_site]
 
-    test "creates a spike notification record with the user email", %{conn: conn, site: site, user: user} do
+    test "creates a spike notification record with the user email", %{
+      conn: conn,
+      site: site,
+      user: user
+    } do
       post(conn, "/sites/#{site.domain}/spike-notification/enable")
 
       notification = Repo.get_by(Plausible.Site.SpikeNotification, site_id: site.id)
@@ -420,7 +427,10 @@ defmodule PlausibleWeb.SiteControllerTest do
 
     test "updates spike notification threshold", %{conn: conn, site: site} do
       insert(:spike_notification, site: site, threshold: 10)
-      put(conn, "/sites/#{site.domain}/spike-notification", %{"spike_notification" => %{"threshold" => "15"}})
+
+      put(conn, "/sites/#{site.domain}/spike-notification", %{
+        "spike_notification" => %{"threshold" => "15"}
+      })
 
       notification = Repo.get_by(Plausible.Site.SpikeNotification, site_id: site.id)
       assert notification.threshold == 15
@@ -433,7 +443,9 @@ defmodule PlausibleWeb.SiteControllerTest do
     test "adds a recipient to the spike notification", %{conn: conn, site: site} do
       insert(:spike_notification, site: site)
 
-      post(conn, "/sites/#{site.domain}/spike-notification/recipients", recipient: "user@email.com")
+      post(conn, "/sites/#{site.domain}/spike-notification/recipients",
+        recipient: "user@email.com"
+      )
 
       report = Repo.get_by(Plausible.Site.SpikeNotification, site_id: site.id)
       assert report.recipients == ["user@email.com"]
diff --git a/test/support/clickhouse_setup.ex b/test/support/clickhouse_setup.ex
index 73ba2e5145e48b03ed20132b90e08af9e17a3238..89f085c0a3528946bd287916a53e768f940196b6 100644
--- a/test/support/clickhouse_setup.ex
+++ b/test/support/clickhouse_setup.ex
@@ -17,7 +17,7 @@ defmodule Plausible.Test.ClickhouseSetup do
         referrer_source: "10words",
         referrer: "10words.com/page1",
         timestamp: ~N[2019-01-01 00:00:00],
-        session_id: @conversion_1_session_id,
+        session_id: @conversion_1_session_id
       },
       %{
         name: "pageview",
diff --git a/test/workers/clean_email_verification_codes_test.exs b/test/workers/clean_email_verification_codes_test.exs
index 7062d637eeeaf2fa919cb7e409f57d0b0085bc1d..ae778065d926846fd050d7c49163d7f817966c95 100644
--- a/test/workers/clean_email_verification_codes_test.exs
+++ b/test/workers/clean_email_verification_codes_test.exs
@@ -3,8 +3,14 @@ defmodule Plausible.Workers.CleanEmailVerificationCodesTest do
   alias Plausible.Workers.CleanEmailVerificationCodes
 
   defp issue_code(user, issued_at) do
-    code = Repo.one(from(c in "email_verification_codes", where: is_nil(c.user_id), select: c.code, limit: 1))
-    Repo.update_all(from(c in "email_verification_codes", where: c.code == ^code), [set: [user_id: user.id, issued_at: issued_at]])
+    code =
+      Repo.one(
+        from(c in "email_verification_codes", where: is_nil(c.user_id), select: c.code, limit: 1)
+      )
+
+    Repo.update_all(from(c in "email_verification_codes", where: c.code == ^code),
+      set: [user_id: user.id, issued_at: issued_at]
+    )
   end
 
   test "cleans codes that are more than 4 hours old" do
diff --git a/test/workers/spike_notifier_test.exs b/test/workers/spike_notifier_test.exs
index 22a2ce158fde98ffe3fd6f33b3da1e3eef23eb02..d0fd500a2f6c8beea07ee3ce06a2f7125265a316 100644
--- a/test/workers/spike_notifier_test.exs
+++ b/test/workers/spike_notifier_test.exs
@@ -6,10 +6,17 @@ defmodule Plausible.Workers.SpikeNotifierTest do
 
   test "does not notify anyone if current visitors does not exceed notification threshold" do
     site = insert(:site)
-    insert(:spike_notification, site: site, threshold: 10, recipients: ["jerod@example.com", "uku@example.com"])
 
-    clickhouse_stub = stub(Plausible.Stats.Clickhouse, :current_visitors, fn _site, _query -> 5 end)
-                      |> stub(:top_sources, fn _site, _query, _limit, _page, _show_noref -> [] end)
+    insert(:spike_notification,
+      site: site,
+      threshold: 10,
+      recipients: ["jerod@example.com", "uku@example.com"]
+    )
+
+    clickhouse_stub =
+      stub(Plausible.Stats.Clickhouse, :current_visitors, fn _site, _query -> 5 end)
+      |> stub(:top_sources, fn _site, _query, _limit, _page, _show_noref -> [] end)
+
     SpikeNotifier.perform(nil, nil, clickhouse_stub)
 
     assert_no_emails_delivered()
@@ -17,10 +24,17 @@ defmodule Plausible.Workers.SpikeNotifierTest do
 
   test "notifies all recipients when traffic is higher than configured threshold" do
     site = insert(:site)
-    insert(:spike_notification, site: site, threshold: 10, recipients: ["jerod@example.com", "uku@example.com"])
 
-    clickhouse_stub = stub(Plausible.Stats.Clickhouse, :current_visitors, fn _site, _query -> 10 end)
-                      |> stub(:top_sources, fn _site, _query, _limit, _page, _show_noref -> [] end)
+    insert(:spike_notification,
+      site: site,
+      threshold: 10,
+      recipients: ["jerod@example.com", "uku@example.com"]
+    )
+
+    clickhouse_stub =
+      stub(Plausible.Stats.Clickhouse, :current_visitors, fn _site, _query -> 10 end)
+      |> stub(:top_sources, fn _site, _query, _limit, _page, _show_noref -> [] end)
+
     SpikeNotifier.perform(nil, nil, clickhouse_stub)
 
     assert_email_delivered_with(
@@ -37,8 +51,10 @@ defmodule Plausible.Workers.SpikeNotifierTest do
   test "does not notify anyone if a notification already went out in the last 12 hours" do
     site = insert(:site)
     insert(:spike_notification, site: site, threshold: 10, recipients: ["uku@example.com"])
-    clickhouse_stub = stub(Plausible.Stats.Clickhouse, :current_visitors, fn _site, _query -> 10 end)
-                      |> stub(:top_sources, fn _site, _query, _limit, _page, _show_noref -> [] end)
+
+    clickhouse_stub =
+      stub(Plausible.Stats.Clickhouse, :current_visitors, fn _site, _query -> 10 end)
+      |> stub(:top_sources, fn _site, _query, _limit, _page, _show_noref -> [] end)
 
     SpikeNotifier.perform(nil, nil, clickhouse_stub)