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