towerops/lib/towerops_web/controllers/health_controller.ex
2026-01-28 17:02:36 -06:00

106 lines
2.9 KiB
Elixir

defmodule ToweropsWeb.HealthController do
@moduledoc false
use ToweropsWeb, :controller
alias Ecto.Adapters.SQL
alias Towerops.Repo
def index(conn, _params) do
require Logger
db_status = check_database()
redis_status = check_redis()
# Log health check results for debugging
Logger.debug("Health check: db=#{inspect(db_status)}, redis=#{inspect(redis_status)}")
all_healthy = db_status == :ok and redis_status in [:ok, :not_configured]
if all_healthy do
conn
|> put_resp_content_type("application/json")
|> send_resp(
200,
Jason.encode!(%{
status: "ok",
database: "connected",
redis: redis_status_string(redis_status),
version: :towerops |> Application.spec(:vsn) |> to_string()
})
)
else
conn
|> put_resp_content_type("application/json")
|> send_resp(
503,
Jason.encode!(%{
status: "error",
database: db_status_string(db_status),
redis: redis_status_string(redis_status)
})
)
end
end
defp check_database do
case SQL.query(Repo, "SELECT 1", []) do
{:ok, _} -> :ok
{:error, _} -> :error
end
end
defp check_redis do
require Logger
redis_config = Application.get_env(:towerops, :redis, [])
if Keyword.has_key?(redis_config, :host) do
# Redis is configured, check connectivity with short timeout for health check
case Towerops.RedisHealthCheck.check_health(redis_config, 2_000) do
:ok ->
:ok
{:error, reason} ->
Logger.error("Redis health check failed: #{inspect(reason)}")
:error
end
else
# Redis not configured (e.g., in development without Redis)
Logger.debug("Redis not configured")
:not_configured
end
end
defp db_status_string(:ok), do: "connected"
defp db_status_string(:error), do: "disconnected"
defp redis_status_string(:ok), do: "connected"
defp redis_status_string(:error), do: "disconnected"
defp redis_status_string(:not_configured), do: "not_configured"
@doc """
Time diagnostic endpoint for debugging TOTP issues.
Returns server time from multiple sources.
"""
def time(conn, _params) do
system_time = System.system_time(:second)
os_time = :os.system_time(:second)
datetime_now = DateTime.utc_now()
conn
|> put_resp_content_type("application/json")
|> send_resp(
200,
Jason.encode!(%{
system_time: system_time,
system_time_iso: system_time |> DateTime.from_unix!() |> DateTime.to_iso8601(),
os_time: os_time,
os_time_iso: os_time |> DateTime.from_unix!() |> DateTime.to_iso8601(),
datetime_now: DateTime.to_unix(datetime_now),
datetime_now_iso: DateTime.to_iso8601(datetime_now),
time_sources_agree: system_time == os_time and system_time == DateTime.to_unix(datetime_now)
})
)
end
end