analytics/lib/plausible_web/controllers/api/external_controller.ex
hq1 99fe03701e
IP Block List (#3761)
* Add Ecto.Network dependency

* Migration: Add ip block list table

* If Cachex errors out, mark the cache as not ready

* Add IPRule schema

* Seed IPRules

* Add Shields context module

* Implement IPRuleCache

* Start IPRuleCache

* Drop blocklisted IPs on ingestion

* Cosmetic rename

* Add settings sidebar item

* Consider IPRuleCache readiness on health checks

* Fix typo

* Implement IP blocklist live view

* Update moduledocs

* Extend contextual module tests

* Convert IPRules LiveView into LiveComponent

* Keep live flashes on the tabs view

* Update changelog

* Format

* Credo

* Remove garbage

* Update drop reason typespecs

* Update typespecs for cache keys

* Keep track of who added a rule and when

* Test if adding via LV prefills the updated_by tooltip

* Update ecto_network dependency

* s/updated_by/added_by

* s/drop_blocklist_ip/drop_shield_rule_ip

* Add docs link

* s/Updated/Added
2024-02-12 14:55:20 +01:00

107 lines
2.8 KiB
Elixir

defimpl FunWithFlags.Actor, for: BitString do
def id(str) do
str
end
end
defmodule PlausibleWeb.Api.ExternalController do
use PlausibleWeb, :controller
require Logger
alias Plausible.Ingestion
def event(conn, _params) do
with {:ok, request} <- Ingestion.Request.build(conn),
_ <- Sentry.Context.set_extra_context(%{request: request}) do
case Ingestion.Event.build_and_buffer(request) do
{:ok, %{dropped: [], buffered: _buffered}} ->
conn
|> put_status(202)
|> text("ok")
{:ok, %{dropped: dropped, buffered: _}} ->
first_invalid_changeset = find_first_invalid_changeset(dropped)
if first_invalid_changeset do
conn
|> put_resp_header("x-plausible-dropped", "#{Enum.count(dropped)}")
|> put_status(400)
|> json(%{errors: Plausible.ChangesetHelpers.traverse_errors(first_invalid_changeset)})
else
conn
|> put_resp_header("x-plausible-dropped", "#{Enum.count(dropped)}")
|> put_status(202)
|> text("ok")
end
end
else
{:error, %Ecto.Changeset{} = changeset} ->
conn
|> put_status(400)
|> json(%{errors: Plausible.ChangesetHelpers.traverse_errors(changeset)})
end
end
def error(conn, _params) do
Sentry.capture_message("JS snippet error")
send_resp(conn, 200, "")
end
def health(conn, _params) do
postgres_health =
case Ecto.Adapters.SQL.query(Plausible.Repo, "SELECT 1", []) do
{:ok, _} -> "ok"
e -> "error: #{inspect(e)}"
end
clickhouse_health =
case Ecto.Adapters.SQL.query(Plausible.ClickhouseRepo, "SELECT 1", []) do
{:ok, _} -> "ok"
e -> "error: #{inspect(e)}"
end
cache_health =
if postgres_health == "ok" and Plausible.Site.Cache.ready?() and
Plausible.Shield.IPRuleCache.ready?() do
"ok"
end
status =
case {postgres_health, clickhouse_health, cache_health} do
{"ok", "ok", "ok"} -> 200
_ -> 500
end
put_status(conn, status)
|> json(%{
postgres: postgres_health,
clickhouse: clickhouse_health,
sites_cache: cache_health
})
end
def info(conn, _params) do
build =
:plausible
|> Application.get_env(:runtime_metadata)
|> Keyword.take([:version, :commit, :created, :tags])
|> Map.new()
geo_database = Plausible.Geo.database_type() || "(not configured)"
json(conn, %{
geo_database: geo_database,
build: build
})
end
defp find_first_invalid_changeset(dropped) do
Enum.find_value(dropped, nil, fn dropped_event ->
case dropped_event.drop_reason do
{:error, %Ecto.Changeset{} = changeset} -> changeset
_ -> false
end
end)
end
end